Java双重排除锁单例真的线程放心吗?

双重检查锁单例模式

乍一看下面单例模式没啥问题,还加了同步锁保证线程安全,从表面上看确实看不出啥问题,当在同一时间多个线程同时执行该单例时就会出现JVM指令重排的问题,从而可能导致某一个线程获取的single对象未初始化对象。


  1. public class Single { 
  2.  
  3.  private static Single single; 
  4.   
  5.  private Single() { 
  6.  } 
  7.   
  8.  public static Single getInstance() { 
  9.   if(null == single) { 
  10.    synchronized (Single.class) { 
  11.     if(null == single) { 
  12.      single = new Single(); 
  13.     } 
  14.    } 
  15.   } 
  16.   return single; 
  17.  } 

问题前因后果

其实single = new Single()这段代码并不具备原子性,从代码层面上来说确实没问题,但是如果了解JVM指令的就知道其实在执行这句代码的时候在JVM中是需要执行三个指令来完成的,如下:


  1. //1:分配对象的内存空间 
  2. memory = allocate();  
  3. //2:初始化对象 
  4. ctorInstance(memory);  
  5. //3:设置instance指向刚分配的内存地址 
  6. instance = memory;  

看到上面指令重排的解释之后,那么我们来回顾一下未加volatile修饰符的单例为何会出现问题。假设有A、B两个线程去调用该单例方法,当A线程执行到single = new Single()时,如果编译器和处理器对指令重新排序,指令重排后:


  1. //1:分配对象的内存空间 
  2. memory = allocate();  
  3. //3:设置instance指向刚分配的内存地址,此时对象还没被初始化 
  4. instance = memory;  
  5. //2:初始化对象 
  6. ctorInstance(memory);  

当A线程执行到第二步(3:设置instance指向刚分配的内存地址,此时对象还没被初始化)变量single指向内存地址之后就不为null了,此时B线程进入第一个if,由于single已经不为null了,那么就不会执行到同步代码块,而是直接返回未初始化对象的变量single,从而导致后续代码报错。

解决方案

问题也搞清楚了,接下来我们就来看下如何解决这个问题。解决问题的关键就在于volatile关键字,原因就在于它的可见性:

写volatile修饰的变量时,JMM会把本地内存中值刷新到主内存 读

volatile修饰的变量时,JMM会设置本地内存无效

在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的,Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。

当然,synchronize和Lock都可以保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。

【声明】:芜湖站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。

相关文章