单例模式是一种常用的设计模式,其目的是确保一个类只有一个实例(对象),并提供一个全局访问点。单例模式有两种常见的实现方式:饿汉式和懒汉式。
一、饿汉式
饿汉式在类加载时就完成了实例化。因为类加载是线程安全的,所以在多线程环境下也是安全的。这种方式比较简单,但是由于实例在类加载时就创建,即使没有被使用,也会占用内存资源。
步骤:
1.私有化构造函数
解释 :当私有化构造函数那么其他类 就不能创建该类的对象,相当于该类的一切非静态的成员在其他类中 都访问不了了!!!当你使用new创建对象时会调用其构造方法,因为构造函数私有化了,所以创建不了对象,创建不了对象相当于一切非静态的成员在其他类中都访问不了。
2.创建私有的静态类的对象
解释 :为什么是私有的?防止其他类直接调用对象从而破坏单例的唯一性。为什么是静态的?如果不是静态的属性,那么其他类无法创建对象进行访问。
3.创建公共的静态get方法获取静态类的对象!!!
解释:该操作类似于封装私有的成员变量然后只能通过公共的set和get方法来获取,但是改操作是一个静态的方法,静态的方法只能调用静态的成员变量不能直接调用非静态的成员。
代码示例:
public class Person {
private String name;
// 在类加载时就创建实例
private static Person person = new Person("小明");
// 私有构造函数,防止外部实例化
private Person(String name){
this.name=name;
}
// 提供全局访问点
public static Person getInstance(){
return person;
}
@Override
public String toString() {
return name;
}
}
class Test{
public static void main(String[] args) {
Person person1 = Person.getInstance();
Person person2 = Person.getInstance();
System.out.println(person1);
System.out.println(person2);
System.out.println(person1==person2);
}
}
结果如下:
小明
小明
true
解释:这段代码,我们只能通过静态的getInstance()方法来获取Person对象,并且只能获取一个对象,因为在其他类(比如Test类)创建不了Person对象而只能获取一个Person对象,当我们再次调用静态的getInstance()方法来获取Person对象时,本质上是获取同一个对象,所以通过"=="进行比较得到的结果是true。
疑问1:为什么输出的是小明?
答:因为Person类重写了toString方法。
疑问2:为什么name属性不是静态的属性?
答:在你不需要调用name属性时不需要写成静态的属性。
疑问3:为什么实例(对象)在类加载时就创建?
答:因为在类加载时会进行静态属性的初始化和执行静态代码块。
参考文献:类什么时候加载?-CSDN博客
优点:
- 线程安全:因为实例在类加载时就创建,天然线程安全。
- 实现简单:代码量少,容易理解。
缺点:
- 资源浪费:如果实例从未被使用,仍然会占用内存。
二、懒汉式
懒汉式在第一次调用getInstance()
方法时才创建实例。这种方式延迟了实例化,节省了资源,但需要考虑线程安全问题。
代码如下:
public class Person {
private String name;
// 声明实例,但不立即创建
private static Person person;
// 私有构造函数,防止外部实例化
private Person(String name){
this.name=name;
}
// 提供全局访问点但非线程安全
public static Person getInstance(){
if (person==null){
person = new Person("小明");
}
return person;
}
@Override
public String toString() {
return name;
}
}
class Test{
public static void main(String[] args) {
Person person1 = Person.getInstance();
Person person2 = Person.getInstance();
System.out.println(person1);
System.out.println(person2);
System.out.println(person1==person2);
}
}
结果如下(和饿汉式代码的结果一样):
小明
小明
true
解释: 与饿汉式的区别是懒汉式在第一次调用getInstance()
方法时才创建实例并不是类加载时就创建实例对象,创建实例对象时首先判断对象是否存在,不存在则创建对象,存在则返回已存在的对象。
线程安全问题
在懒汉式单例模式中,如果多个线程同时调用getInstance()
方法,并且此时实例尚未创建(即instance
变量为null
),那么这些线程都可能进入创建实例的代码块。如果没有适当的同步机制,就可能导致多个线程同时创建实例,从而违反单例模式的原则。
优点:
- 延迟实例化,节省资源。
缺点:
- 实现复杂:需要考虑线程安全问题。
饿汉式和懒汉式的选择取决于具体的应用场景。如果单例对象较大且创建过程耗时,或者类加载时间较长,可以考虑使用懒汉式。如果单例对象较小且创建过程简单,或者类加载时间可以接受,可以考虑使用饿汉式。同时,懒汉式需要考虑线程安全问题,而饿汉式则不需要。