锁定老帖子 主题:有必要普及一下关于单例模式的常识.
精华帖 (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 上有详细描述的 我回去翻翻书吧 |
|
返回顶楼 | |
发表时间:2012-03-30
在多个线程访问同一个变量时,必须使用同步或者volatile。volatile语义保证 volatile 字段的读写直接在主存而不是寄存器或者本地处理器缓存中进行,并且代表线程对 volatile 变量进行的这些操作是按线程要求的顺序进行的。换句话说,这意味着老的内存模型保证正在读或写的变量的可见性,不保证写入其他变量的可见性。Volatile变量可以与对非volatile 变量的读写一起重新排序。
今天读到 贴上来 |
|
返回顶楼 | |
发表时间:2012-03-30
如果当线程 A 写入 volatile 变量 V,而线程 B 读取 V 时,那么在写入 V 时,A 可见的所有变量值现在都可以保证对 B 是可见的。代价是访问 volatile 字段时会对性能产生更大的影响。
|
|
返回顶楼 | |
发表时间: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语句块中还会存在指令重排的问题吗? 非常期待你的解释。 |
|
返回顶楼 | |
发表时间:2012-03-31
phk070832 写道 我并没有说double-check不存在问题,而只是说有没有volatile都一样。 你这个是《java thread》上看到的吧。你能解释一下为什么在synchronized语句块中还会存在指令重排的问题吗? 非常期待你的解释。 没看过<<java thread>>,讲计算机体系结构的书基本都讲了CPU流水线指令重拍问题。 没有谁规定synchronized块中不存在指令重拍问题。 只要没有插入memory barrier 指令,jdk管不了cpu进不进行指令重拍,难道jvm实现在synchronized块中的每条指令都插入一条内存障碍吗? |
|
返回顶楼 | |
发表时间:2012-03-31
期待楼上你们2人最终统一的结果
|
|
返回顶楼 | |
发表时间:2012-04-01
正是所需啊
|
|
返回顶楼 | |
发表时间: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看到一个完整初始化的实例。 |
|
返回顶楼 | |
发表时间:2012-04-01
有了voliate 就保证了内存可见性
|
|
返回顶楼 | |