疑问
volatile关键字和synchronized关键字。
synchronized可以保证原子性、有序性和可见性。
而volatile却只能保证有序性和可见性。
注意:这两个有序的意思是不一样的。
那么,我们再来看一下双重校验锁实现的单例,已经使用了synchronized,为什么还需要volatile?这个volatile是否可以去掉?
public class SingleInstance{
    // 使用 volatile 禁止 new 对象时进行指令重排
    // new 对象,有三条指令完成
    // 1 JVM为对象分配一块内存M在堆区
    // 2 在内存M上为对象进行初始化
    // 3 将内存M的地址复制给instance变量
    private volatile static SingleInstance instance;
    private SingleInstance(){
        // 反射可以直接调用构造方法,绕过private的限制
        // 这里进行避免
        // 反序列化咋搞?
        if(instance != null){
            throw new RuntimeException("single instance existed");
        }
    }
    public SingaleInstance getInstance(){
        // 减少锁竞争,避免过多的线程进入同步队列,进入 blocking 状态
        if(instance == null){
            // 保证只有一个线程可以实例化对象,其他线程进入同步队列blocking
            // 这个 syn 可以保证原子性和可见性
            // 而 有序性,指的是 保证线程串行进入syn 代码块内
            // 所以,此有序性无法保证 syn代码块 内部的有序性
            synchronized(SingleInstance.class){
                // 避免重复创建对象
                if(instance == null){
                    instance = new SingleInstance();
                }
            }
        }
        return instance;
    }    
}
由于 synchronized 是不能保证指令重排的,所以,可能会出问题。
new 对象的三个步骤(简化)
- JVM为对象分配一块内存M。
- 在内存M上为对象进行初始化。
- 将内存M的地址复制给singleton变量。
这个步骤有两种执行顺序可以按照 ①②③或者①③②来执行。
当我们按照①③②的顺序来执行的时候:
我们假设有两个线程ThreadA 和ThreadB同时来请求SingleInstance.getInstance方法:
正常情况按照 ①②③的顺序来执行
- ThreadA 进入到同步代码块,执行 instance = new SingleInstance()进行对象的初始化(按照对象初始化的过程 ①②③)执行完。
- ThreadB进入第5行判断instance不为空(已经初始化好了),直接返回instance
- 拿到这个对象做其他的操作。
这样看下来是不是没有啥问题。
但是,如果对象初始化的时候按照 ①③② 的步骤我们再来看看:
- ThreadA进入到同步代码块,执行 instance = new SingleInstance()执行完; ①JVM为对象分配一块内存M。③将内存的地址复制给singleton变量。
- 此时ThreadB直接进入到同步代码块,发现instance已经不为空了然后直接就跳转到12行拿到这个instance返回去执行操作去了。此时ThreadB拿到的singleton对象是个半成品对象,因为还没有为这个对象进行初始化(②还没执行)。
- 所以, ThreadB拿到的对象去执行方法可能会有异常产生。至于为什么会这样列?《Java 并发编程实战》有提到
有 synchronized 无 volatile 的 DCL(双重检查锁) 会出现的情况:线程可能看到引用的当前值,但对象的状态值确少失效的,这意味着线程可以看到对象处于无效或错误的状态。
说白了也就是ThreadB是可以拿到一个引用已经有了但是内存资源还没有分配的对象。
解决
解决指令重排只要给 instance 加个
volatile修饰就好
synchronized 和 volatile的有序性比较
 
- synchronized的有序性:是持有相同锁的两个同步块只能串行的进入,即被加锁的内容要按照顺序被多个线程执行,但是其内部的同步代码还是会发生重排序,使块与块之间有序可见。
- volatile的有序性:是通过插入内存屏障来保证指令按照顺序执行。不会存在后面的指令跑到前面的指令之前来执行。是保证编译器优化的时候不会让指令乱序。
附录
https://blog.csdn.net/zengfanwei1990/article/details/110245035
 
                       
                             
                         
                             
                             
                           
                           
                            
 
                             本文深入探讨了双重检查锁(DCL)模式下,为何需要使用volatile关键字,即使已有synchronized关键字确保原子性和可见性。文章详细解释了指令重排序带来的问题,并通过具体示例展示了在特定条件下未使用volatile修饰可能导致的问题。
本文深入探讨了双重检查锁(DCL)模式下,为何需要使用volatile关键字,即使已有synchronized关键字确保原子性和可见性。文章详细解释了指令重排序带来的问题,并通过具体示例展示了在特定条件下未使用volatile修饰可能导致的问题。
           
       
           
                 
                 
                 
                 
                 
                
               
                 
                 
                 
                 
                
               
                 
                 扫一扫
扫一扫
                     
              
             
                   3287
					3287
					
 被折叠的  条评论
		 为什么被折叠?
被折叠的  条评论
		 为什么被折叠?
		 
		  到【灌水乐园】发言
到【灌水乐园】发言                                
		 
		 
    
   
    
   
             
            


 
            