懒汉单例模式基础上,加上双检查锁机制,此时 “单例” 还需要加 volatile 关键字,这是为了消除处理器乱序执行的干扰吗?
如果是的话,谁能举出一个不加 volatile 关键字的情况下导致非单例的例子。(真实代码情景)
我换个方式描述问题吧,有人不理解我的问题。
双重检测在新的内存模型下能很好地工作吗?
private static Something instance = null; public Something getInstance() { if (instance == null) { synchronized (this) { if (instance == null) instance = new Something();//1 } } return instance; }
此时可能得到一个不为空的但是还未初始化的对象。因为乱序写入引起的。
如果加上 volatile 就能解决这个问题。
这个理论我已经非常清楚了。
我想知道的是哪位大神能举个例子复现一下这个 不为空的但是还未初始化的对象。
这个跟单例没有关系吧,volatile是指示程序不要直接从寄存器中取值:
int a = 10;
int b = a;
int c = a;
理论上来讲每次使用a的时候都应该从a的地址来读取变量值,但是这存在一个效率问题,就是每次使用a都要去内存中取变量值,然后再通过系统总线传到CPU处理,这样开销会很大。所以编译器会把a写进CPU寄存器里,像上面的代码,假如a在赋值期间没有被改变,就直接从CPU寄存器里取a的副本来进行赋值。但是bug也显而易见,当a在赋给b之后,可能a已经被另一个线程改变而重新写回了内存,但这个线程并不知道,依旧按照原来的计划从CPU寄存器里读a的副本进来赋值给c,就导致错误取值。
这时,就可以使用volatile来避免此类问题的发生,指示从变量地址取值,而不是CPU寄存器。
这个理论我懂,怎样才能复现不用 volatile 关键字出现指令重排的情况。
@屌丝的烦恼: 这个不是很好重现,在特定条件下,编译器才会进行优化,而且要在release模式,跟cpu也有关系。
bool flag=true;
long x=0;
var td=new Thread(()=>{
while(flag)
{x++;}
Console.WriteLine("this should not be shown");
});
td.Start();
Thread.Sleep(1000);
flag=false;
Console.WriteLine("oops");
Console.ReadLine();
java的不太会,你改下吧。
您给的这是什么鬼?
@屌丝的烦恼: volatile关键字的意义啊。那个flag加不加volatile结果不一样的。
@Daniel Cai: 这个和乱序执行有关系吗?详细解释一下?
@Daniel Cai: 网上一堆 volatile 禁止指令重排列的理论知识,但是找不到一个具体的例子对比。
@屌丝的烦恼: 这个代码倒和乱序关系不大,唯一有点关联的就是volatile关键字了。如果没有这个关键字运行时可能会和代码执行的顺序不一致(忽略编译器优化)
但有了volatile关键字相当于有了full fence
类似
Thread.MemoryBarrier();
...
Thread.MemoryBarrier();
cpu在执行这样的逻辑只会按照代码中的线性逻辑去执行。
前面那个例子主要是说明volatile关键字会强制读取内存而不是寄存器中的数据。
@Daniel Cai: 。。。。我需要的是:网上一堆 volatile 禁止指令重排列的理论知识,但是找不到一个具体的例子对比。这么个例子。
就像上楼说的,这样的例子不好重现。。。
@屌丝的烦恼: http://stackoverflow.com/questions/2441279/volatile-guarantees-and-out-of-order-execution
@Daniel Cai: 还是没有解决问题哦。都是理论都是理论。我所要的例子对比还是没人举出来。
@屌丝的烦恼: 乱序是执行的时候,结果还是一样,你又没办法通过相同的结果看出来什么。
比如int i=1;
int j=2;
先执行后面再执行前面最后结果都是一样,你还能要什么例子。
@Daniel Cai: 单线程的时候结果一样,多线程就不一定了。说了也挺多了。看一下问题补充,换种方式问。
@屌丝的烦恼: 这个double check是跟jvm有关的吧,这个信息你最好去查下那个很有名的double check的文章。