解读并发多线程中dcl(double check lock)的时候被操作共享变量需要使用volatile进行修饰

下边拿dcl单例模式进行举例说明:

public class Singleton {  
    private int num=100;
    private static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

曾经误以为是jmm的模型中,后续线程T2中工作内存副本中先得到了null(通过了第一次null判断,之后别的线程T1获取锁创建了实例但是未能及时通知T2改变)未被及时通知变化导致的了,所以导致进入在第二次为空判断时还是null,后来知道这么理解是错误的,看下边红字就知道了

补充一些知识:

java内存模型与synchronized关键字

synchronized关键字强制实施一个互斥锁,使得被保护的代码块在同一时间只能有一个线程进入并执行。当然synchronized还有另外一个 方面的作用:在线程进入synchronized块之前,会把工作存内存中的所有内容映射到主内存上,然后把工作内存清空再从主存储器上拷贝最新的值(经测试sleep,wait,park这些主动释放cup的操作都有类似作用)。而 在线程退出synchronized块时,同样会把工作内存中的值映射到主内存,但此时并不会清空工作内存。这样一来就可以强制其按照上面的顺序运行,以 保证线程在执行完代码块后,工作内存中的值和主内存中的值是一致的,保证了数据的一致性!  

所以由synchronized修饰的set与get方法都是相当于直接对主内存进行操作,不会出现数据一致性方面的问题。

真正原因:

singleton = new Singleton();

这一步实际的操作可被分为了三步:

1分配内存空间属性赋零值num=0(零值不应定就是0还有可能是false等);

2属性赋初值或构造函数值num=100

3将singleton位置指向此处内存空间

但是这三步有可能被cpu进行指重排序,导致T1在创建singleton时先1后3(则此时singleton已有null变为有真正的指向),若这时T2得到cup进来了发现singleton已不为null,便将其拿入工作副本实用,则会出现问题;

这种概率极小但一旦发生在某些业务中可能就是致命的,所以此处需要禁止指令重排序,而volatile便有这个功能(可见性,禁止指令重排序)

代码改为:

public class Singleton {  
    private int num=100;
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

补充一个关于volitail小知识:

伪共享问题:volitail 会触发缓存一致性[intel用的是MESI协议],缓存一致性每次都是按照行为单位触发的(缓存行的概念),行内可能有多个变量比如A,B  一旦A失效整个行的变量都将失效,(所以如果线程1只用了volitail A,线程2只用了volitail B 但是两个线程的缓存行却都是A和B,那就会触发不必要的缓存一致性同步)

你可能感兴趣的:(解读并发多线程中dcl(double check lock)的时候被操作共享变量需要使用volatile进行修饰)