使用场景
某个对象全局只需一个实例时
优点
- 避免对象重复创建,节省空间并提高效率
- 避免由于操作不同实例导致的逻辑错误
饿汉式实现
变量在声明时就初始化
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
缺点: 即使这个单例不需要使用,它也会在类加载之后立即创建出来,占用一块内存,并增加类初始化时间
懒汉式实现
先声明一个空变量,需要用时才初始化
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
以上为最终版的双检锁方式,我们从最开始一步一步看 假如最开始只有如下代码,我们一步步优化
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
1.它并不是线程安全的。所以如果有多个线程同一时间调用 getInstance 方法,instance 变量可能会被实例化多次。为了保证线程安全,我们需要给判空过程加上锁,这就有了synchronized。 2.当多个线程调用 getInstance 时,每次都需要执行 synchronized 同步化方法,这样会严重影响程序的执行效率。所以更好的做法是在同步化之前,再加上一层检查
注意:在外面检查了 instance == null, 锁里面的空检查仍不可以去掉
如果里面不做空检查,可能会有两个线程同时通过了外面的空检查,然后在一个线程 new 出实例后,第二个线程进入锁中又 new 出一个实例,导致创建多个实例。
volatile作用: 防止指令重排序
instance = new Singleton();
这一行代码包括三条指令:
- 分配对象的内存空间
- 初始化对象
- 将变量 instance 指向刚分配的内存空间
如果第二条指令和第三条指令发生了重排序,可能导致 instance 还未初始化时,其他线程提前通过双检锁外层的 null 检查,获取到“不为 null,但还没有执行初始化”的 instance 对象,发生空指针异常。
|