论坛首页 综合技术论坛

有必要普及一下关于单例模式的常识.

浏览 20115 次
精华帖 (2) :: 良好帖 (10) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-03-28  
情已逝 写道
phk070832 写道
spyker 写道
phk070832 写道
池中物 写道
。。。


这个根本就不是有没有volatile,因为在同步块中,数据都会从寄存器中被刷到内存中。


。。。


这个不是加volatile有错,而是加不加volatile是一件无所谓的事情


   if(instance == null){    // first check 
     synchronized(DoubleCheckLockSingleton.class){  // 因为这里已经有个synchronized,数据会被刷新到内存中
      if(instance == null){  //second check 
         instance =new DoubleCheckLockSingleton(); 
         } 
       } 
     } 



我都怀疑你是否知道double-check存在bug的原因在哪里?


if(instance == null){    // a
     synchronized(DoubleCheckLockSingleton.class){ 
      if(instance == null){  //second check 
         instance =new DoubleCheckLockSingleton(); // b 
         } 
       } 
     } 

这段代码的问题在于2个线程分别在a,b行时,可能存在b行位置的执行还没完成,a行判断已经不为null了。

产生这个情况的根本原因是指令重排,一些编译器会做指令重排优化,即使编译器不做,CPU为了流水线指令的最优执行也会做指令重排,称为乱序执行(out-of-order)。
假设 instance = new DoubleCheckLockSingleton() 的执行可简单分为3步
1.分配内存
2.执行构造函数
3.给instance赋值。
但是在编译器或cpu看来,第3步是可能在第2步之前执行的(因为不影响结果),如果这样就可能出现对象构造函数还没完成,a行判断已经不为null的情况,这个时候对返回instance的调用当然就会出现问题。

如果给instance加上volatile关键字就不一样了,volatile关键字最终会在访问该变量的时候加上内存障碍指令(barrier()),就是在内存障碍指令后的代码一定不会在前面执行,这样保证了顺序不会打乱。



effec java 上有详细描述的
我回去翻翻书吧

0 请登录后投票
   发表时间:2012-03-30  
在多个线程访问同一个变量时,必须使用同步或者volatile。volatile语义保证 volatile 字段的读写直接在主存而不是寄存器或者本地处理器缓存中进行,并且代表线程对 volatile 变量进行的这些操作是按线程要求的顺序进行的。换句话说,这意味着老的内存模型保证正在读或写的变量的可见性,不保证写入其他变量的可见性。Volatile变量可以与对非volatile 变量的读写一起重新排序。



今天读到  贴上来
0 请登录后投票
   发表时间:2012-03-30  
如果当线程 A 写入 volatile 变量 V,而线程 B 读取 V 时,那么在写入 V 时,A 可见的所有变量值现在都可以保证对 B 是可见的。代价是访问 volatile 字段时会对性能产生更大的影响。
0 请登录后投票
   发表时间:2012-03-31   最后修改:2012-03-31
情已逝 写道
phk070832 写道
spyker 写道
phk070832 写道
池中物 写道
。。。


这个根本就不是有没有volatile,因为在同步块中,数据都会从寄存器中被刷到内存中。


。。。


这个不是加volatile有错,而是加不加volatile是一件无所谓的事情


   if(instance == null){    // first check 
     synchronized(DoubleCheckLockSingleton.class){  // 因为这里已经有个synchronized,数据会被刷新到内存中
      if(instance == null){  //second check 
         instance =new DoubleCheckLockSingleton(); 
         } 
       } 
     } 



我都怀疑你是否知道double-check存在bug的原因在哪里?


if(instance == null){    // a
     synchronized(DoubleCheckLockSingleton.class){ 
      if(instance == null){  //second check 
         instance =new DoubleCheckLockSingleton(); // b 
         } 
       } 
     } 

这段代码的问题在于2个线程分别在a,b行时,可能存在b行位置的执行还没完成,a行判断已经不为null了。

产生这个情况的根本原因是指令重排,一些编译器会做指令重排优化,即使编译器不做,CPU为了流水线指令的最优执行也会做指令重排,称为乱序执行(out-of-order)。
假设 instance = new DoubleCheckLockSingleton() 的执行可简单分为3步
1.分配内存
2.执行构造函数
3.给instance赋值。
但是在编译器或cpu看来,第3步是可能在第2步之前执行的(因为不影响结果),如果这样就可能出现对象构造函数还没完成,a行判断已经不为null的情况,这个时候对返回instance的调用当然就会出现问题。

如果给instance加上volatile关键字就不一样了,volatile关键字最终会在访问该变量的时候加上内存障碍指令(barrier()),就是在内存障碍指令后的代码一定不会在前面执行,这样保证了顺序不会打乱。



我并没有说double-check不存在问题,而只是说有没有volatile都一样。
你这个是《java thread》上看到的吧。你能解释一下为什么在synchronized语句块中还会存在指令重排的问题吗?
非常期待你的解释。
0 请登录后投票
   发表时间:2012-03-31  
phk070832 写道

我并没有说double-check不存在问题,而只是说有没有volatile都一样。
你这个是《java thread》上看到的吧。你能解释一下为什么在synchronized语句块中还会存在指令重排的问题吗?
非常期待你的解释。


没看过<<java thread>>,讲计算机体系结构的书基本都讲了CPU流水线指令重拍问题。

没有谁规定synchronized块中不存在指令重拍问题。
只要没有插入memory barrier 指令,jdk管不了cpu进不进行指令重拍,难道jvm实现在synchronized块中的每条指令都插入一条内存障碍吗?
0 请登录后投票
   发表时间:2012-03-31  
期待楼上你们2人最终统一的结果
0 请登录后投票
   发表时间:2012-04-01  
正是所需啊
0 请登录后投票
   发表时间:2012-04-01   最后修改:2012-04-01
情已逝 写道
phk070832 写道

我并没有说double-check不存在问题,而只是说有没有volatile都一样。
你这个是《java thread》上看到的吧。你能解释一下为什么在synchronized语句块中还会存在指令重排的问题吗?
非常期待你的解释。


没看过<<java thread>>,讲计算机体系结构的书基本都讲了CPU流水线指令重拍问题。

没有谁规定synchronized块中不存在指令重拍问题。
只要没有插入memory barrier 指令,jdk管不了cpu进不进行指令重拍,难道jvm实现在synchronized块中的每条指令都插入一条内存障碍吗?


synchronized保证离开synchronized块 happens before 进入synchronized块,即保证在离开synchronized块之前的内存操作们在进入synchronized块时可见。对于这个具体问题,就是线程A在synchronized块中分配内存,执行constructor,给instance赋值这三个内存操作,当线程B进入synchronized块时都可以看到。但是线程B在执行第一个check
     if(instance == null)
因为它不在synchronized块中,没有保证说线程B此时看到线程A在synchronized块中执行的所有三个内存操作。一种可能性就是线程B此时看到的内存操作为分配内存和给instance赋值,即线程B看到了非空的instance,但是还没有初始化完毕,此时线程B操作instance显然会crash。

JDK5内存模型增强了volatile语义,volatile变量写操作 happens before volatile读操作。而happens before关系可传递。因此有如下happens before关系(下面用<表示happens before关系)
    线程A分配内存/执行初始化 < 线程A给instance赋值 < 线程B读取instance
此时线程B执行第一个检查时,如果instance非空,线程B看到一个完整初始化的实例。
0 请登录后投票
   发表时间:2012-04-01  
有了voliate 就保证了内存可见性
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics