public class DCLSingleton { private static DCLSingleton instance; public int value; private DCLSingleton(){ value = 1024; } public static DCLSingleton getInstance(){ if(instance == null){ synchronized (DCLSingleton.class){ if (instance == null){ instance = new DCLSingleton(); //这不是个原子操作,JVM至少分为三个操作去执行 /** * 1.正常情况 * 1.1 address = allocate() * 1.2 <init>,此处会调用DCLSingleton的构造函数,给value赋值1024 * 1.3 instance = address */ /** * 2.指令重排序情况 * 2.1 address = allocate() * 2.2 instance = address 这时候instance已经不为空了,别的线程是可以看到的 * 2.3 <init>,此处会调用DCLSingleton的构造函数,给value赋值1024 */ } } } return instance; } }
DCL单例代码如上,
多线程并发调用时,正常情况下我们期望每个线程拿到的 value值都是1024,但在指令重排序后某些线程拿到value的值可能为0
我们用至少三个线程的环境下,来模拟第二种指令重排后可能会出现的问题的过程:
1.线程A先拿到DCLSingleton.class的锁开始执行,线程B发现锁被占用,只好等待一下了
2.线程A在new DCLSingleton()的过程中,执行完2.2后被cpu踢出
注意:instance已经被赋值,不为空,但DCLSingleton的构造函数还没被调用
3.线程C杀出调用getInstance(), 由于instance已经不为空,C不会同步等待,会直接拿到一个没有调用构造函数的DCLSingleton实例,这时value就是0
4.线程A又开始去执行,接着new的未完成部分,在执行2.3时调用DCLSingleton的构造函数,这时会给value赋值1024
5.此后的线程B与其他线程获得value的值都为1024