单例模式
传统的写法
/**
* 懒汉模式
*/
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
/**
* 饿汉模式
*/
class SingletonWithHungry {
private SingletonWithHungry() {
}
private SingletonWithHungry instance = new SingletonWithHungry();
public SingletonWithHungry getInstance() {
return instance;
}
}
/**
* 枚举单例
*/
public enum Girlfriend{
GIRLFRIEND;
}
/**
* 静态内部类
*/
public class SingleTon{
private SingleTon(){}
private static class SingleTonHoler{
private static SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstance(){
return SingleTonHoler.INSTANCE;
}
}
懒汉模式的线程不安全复现
class TestSingleWithThread {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.err.println(Singleton.getInstance());
});
Thread t2 = new Thread(() -> {
System.err.println(Singleton.getInstance());
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
解决方案:
- 懒人同步——getInstance方法加上synchronized关键字
- DCL——Double Checked Lock双重检查锁定
DCL:
class SingletonThreadSafeDCL {
private static SingletonThreadSafeDCL instance = null;
private SingletonThreadSafeDCL() {
}
public static SingletonThreadSafeDCL getInstance() {
if (instance == null) {//(1)
synchronized (SingletonThreadSafeDCL.class) {//(2)
if (instance == null) {//(3)
instance = new SingletonThreadSafeDCL();//(4)
}
}
}
return instance;
}
}
关于DCL存在的问题,参考《并发编程的艺术》3.8节。
线程执行到(1)时,代码读取到的instance不为null,instance引用的对象可能还没完成初始化
而new SingletonThreadSafeDCL()在JVM中分为三步:
1.在堆内存开辟内存空间。
2.在堆内存中实例化SingleTon里面的各个参数。
3.把对象指向堆内存空间。
由于重排序,可能导致3在2之前执行,
因此,导致线程B访问到了一个未初始化的对象。
比如单例有属性name,在新建实例时name 初始化为Michael,线程B访问到instance时,name未完成初始化,两个线程读到的值不一致。
解决DCL
- volatile,禁止重排序
扩展: volatile happens-before, volatile JMM语义
- 基于类初始化的解决方案