饿汉式单例模式:
public class Hungry {
private Hungry() {}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
}
懒汉式单例:
public class lazyMan {
private lazyMan() {}
private static lazyMan LAZY;
public static lazyMan getInstance() {
if(LAZY == null) { //1:A线程执行
LAZY = new lazyMan(); //2:B线程执行
}
return LAZY;
}
}
懒汉式单例模式的问题
首先声明一点——代码2这一行可以分解成:
- 分配对象的内存空间
- 初始化对象
- 设置LAZY指向刚分配的内存地址
上边是我们任务的顺序,但是编译器可能对这一行代码优化成:
- 分配对象的内存空间
- 设置LAZY指向刚分配的内存地址
- 初始化对象
假设A线程执行代码1的同时,B线程执行代码2,此时线程A可能会看到LAZY引用的对象还没有完成初始化,这肯定会出错;或者另一种情况,线程A和B同时执行到代码1,最终同时进入代码块new对象,返回了两个不同的对象;
解决方案
我们可以对getInstance()方法进行同步处理来解决这种问题:
public class lazyMan {
private lazyMan() {}
private static lazyMan LAZY;
public synchronized static lazyMan getInstance() {
if(LAZY == null) {
LAZY = new lazyMan();
}
return LAZY;
}
}
那么问题来了,我们都知道synchronized 是一个重量级锁,如果getInstance()方法被多个线程频繁调用,将会导致程序执行性能的下降。
解决方案(双重检查锁)
public class lazyMan {
private lazyMan() {}
private volatile static lazyMan LAZY; //1
public static lazyMan getInstance() {
if(LAZY == null) { //2
synchronized (lazyMan.class) {
if(LAZY == null)
LAZY = new lazyMan();
}
}
return LAZY;
}
}
如上面的代码:
- 多个线程同一时间创建对象时,会通过加锁保证只有一个线程创建锁;
- 如果检查LAZY不为空那么就不需要执行下面的加锁和初始化操作,可以大幅的减少synchronized带来的性能开销。
- 代码1处使用volatile
修饰也很有必要,因为synchronized并不能禁止指令重排,使用volatile才能保证多线程情况下线程们看到的LAZY对象时一个初始化完成后的