一个多线程共享变量的问题,请教下大家

工作中遇到了这么一个问题,想请教下大家。伪码如下:

[code="java"]
public class AAAAA{

public boolean enable = true; // [b]非violate[/b]

// 线程A执行
public void methodA{
    while(enable){
        .........
    }
    system.out.println("----------------------");
}

// 线程B执行
public void methodB{
    enable = false;
}

}
[/code]

前提:线程A和线程B一定都会执行code。
问题:线程A是否有可能会永远退不出循环?如果存在这种可能,请详细说明下原因,谢谢。

本人认为不会存在这种情况,但是有同事说存在这种可能,原因是JVM指令重排序优化。我查了些资料,没有找到充足的证据来证明此事,所以想在这里请教下各位大侠,先行谢过了。 :)

luoliudan
luoliudan 是有可能的。在多线程或多处理器的情况下由于缓存不一致以及为了性能而导致的优化措施会导致重复检查过期值。这是一个内存可视性的问题.可以通过volatile,同步,原子变量以及lock接口来避免就可以了。
大约 8 年之前 回复
whg333
whg333 enable只要是非violate的,多线程访问下没有同步确保的话,可能线程会一直看到过期值:true
大约 8 年之前 回复
iteye_5246
iteye_5246 1.静态变量 2.线程同步读取字段的值
大约 8 年之前 回复

15个回答

看看effective java 中文版第二版 230页 如果没错的话 这个描述叫做火星失败 liveness failure 是因为 while(enable) 被jvm 优化成 if(!enable) {
while (true)
} 所以需要设置同步访问 或者是 在enable 前 加上 violate 这样 当enable变化时 这样他能保证任何一个线程在读取该域的时候能看见最新被修改的值

以上全是引用书中的话 若有不全的地方 请看书籍

Evin7
Evin7 谢谢大家的热心帮助,由于活性失败的例子答对了我的问题的一半。而另一半的问题的结论是依赖于具体JVM实现,所以我把最佳答案给了活性失败,望xiaohuafyle见谅。
大约 8 年之前 回复
Evin7
Evin7 如果MethodA变更为“问题补充”中,所提及的,enable在线程A中,还有可能永远为true吗?
大约 8 年之前 回复
Evin7
Evin7 多谢,我去看一下
大约 8 年之前 回复

根据你的伪代码不会出现线程问题吧,你的enable是一个私有变量。
我不知道是不是没有理解你的意思。

Evin7
Evin7 是同一个对象调用,我需要的结果就是A跳出循环。你的意思是,在任何情况下,线程A一定会跳出循环吗?
大约 8 年之前 回复
hao2181
撒旦他舅爷 如果2个线程使用的是同一个对象调用 则有线程安全问题 A就可能跳出 如果2个线程使用不同对象调用 没有线程安全问题 A一直循环
大约 8 年之前 回复
Evin7
Evin7 enable是私有变量,共有变量,还是静态变量都没有关系。重要的是,当线程B修改了enable之后,线程A是否有可能永远都发现不了这个改动?也就是我这里的问题“线程A是否有可能会永远退不出循环?”
大约 8 年之前 回复

多线程 静态字段,都可以修改

Evin7
Evin7 恩,多谢。我这里其实没有强调它一定是静态字段,呵呵。
大约 8 年之前 回复
iteye_5246
iteye_5246 嗯。记得访问静态字段的时候,一定要类名.静态字段名
大约 8 年之前 回复
Evin7
Evin7 那么是不是说,JVM指令重排序优化,对当前我贴出来的这段伪码一定不会有任何影响。也就是说:线程A一定会在某一时刻退出循环?请根据我这段伪码进行回复,多谢。
大约 8 年之前 回复
iteye_5246
iteye_5246 当然静态字段,都可以访问,就行第一个个线程把一个静态字段改为abc,那么第二个线程进来 读取这个静态字段此时,这个字段已经就是修改过了的
大约 8 年之前 回复
Evin7
Evin7 主要是修改之后,是不是其他线程都一定可以获取到最新的值(时间不是问题,主要是能不能够获取的到)
大约 8 年之前 回复

不会出现线程问题 enable是这个成员变量

Evin7
Evin7 多谢,呵呵。我主要是不了解“JVM指令重排序优化”,究竟会不会对程序造成影响,所以提出问题。您的意思是说线程A一定会跳出循环吗?
大约 8 年之前 回复

首先,你确定你同一个AAAAA的对象被A,B线程同时操作了,才会出现线程问题。
假如是的话,除非B线程没有执行到,那么可能会一直执行下去,基本上不太可能,线程B总会改变这个enable。

但是假如说:methodA和methodB都加上同步锁,出现的可能性就极大了。

Evin7
Evin7 呵呵,violate的作用我还算了解,主要是问非violate的情况。多谢。
大约 8 年之前 回复
AngelAndAngel
AngelAndAngel 那啥 我那说的不严谨 你那变量最好 设置violate
大约 8 年之前 回复
Evin7
Evin7 恩,谢谢,我也是一直觉得不可能,主要是被“JVM重排序优化”给唬住了,呵呵。
大约 8 年之前 回复
AngelAndAngel
AngelAndAngel 另外即使再怎么优化 也不会对正确的语法下手 顶多对一个恒等式优化。比如 if(true)这样的东西
大约 8 年之前 回复

程序的本意是希望methodB中断methodA,但是,由于编译器判断在methodA里面没有修改过enable ,因此可能只执行一次对enable 到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“enable 副本”,导致循环永远无法退出,如果将enable加上volatile修饰,则编译器保证对此变量的读写操作作都不会被优化(肯定执行),那么当methodB执行完后,methodA即可退出循环。

iteye_15570
iteye_15570 请不要介意,我在思考你问题的时候,也学到了不少东西,我已经很开心了.
大约 8 年之前 回复
Evin7
Evin7 谢谢你们的热心帮助,由于活性失败的例子答对了我的问题的一半。而另一半的问题的结论是依赖于具体JVM实现,所以我把最佳答案给了活性失败,望xiaohuafyle见谅。
大约 8 年之前 回复
shangqingliang
shangqingliang 线程在MethodA 方法中,只是一次从寄存器读取进来,一直没有改变变量enable的值,一般不会主动更新主存的值到工作内存中,所以很有可能一直下去.
大约 8 年之前 回复
lovexp2010
Jeff-HT-Lee 线程B修改enable线程A一直不可见?我也非常期待答案。看了几位的讨论大概明白了,只能说有可能,依赖于JVM的实现~~
大约 8 年之前 回复
Evin7
Evin7 我觉得这个是与线程的工作内存和主存之间的交互有关系的,是依赖于JVM的实现的。多谢。
大约 8 年之前 回复
iteye_15570
iteye_15570 可能寄存器里面的enable永远不会被更新,这只是可能,因为多线程的运行谁都不能保证结果是什么,这是与不同的系统有关的
大约 8 年之前 回复
iteye_15570
iteye_15570 不是JVM哦,而是jdk的javac
大约 8 年之前 回复
Evin7
Evin7 to xiaohuafyle: 你说的我清楚,我不清楚的是:在这个例子里面,寄存器之中的enable的值有可能再也不会被更新吗?(更新为RAM里面的值)
大约 8 年之前 回复
iteye_9831
iteye_9831 “由于编译器判断在methodA里面”,这里的编译器应该是JVM吧。
大约 8 年之前 回复
iteye_15570
iteye_15570 非violate时,编译器将enable这个变量的值从RAM里拿到寄存器中,每次循环,都检查寄存器中的enable是否为false.而methodB改变的是RAM中的enable的值.这样说你明白吗?
大约 8 年之前 回复
Evin7
Evin7 violate我想我还算是了解的,我主要是说非violate的情况。你是说有可能线程A有可能会永远不更新这个enable的值?这个是依赖于JVM的实现吗?
大约 8 年之前 回复
iteye_15570
iteye_15570 详细关于volatile的细节,请参考http://blog.sina.com.cn/s/blog_62565190010154xd.html
大约 8 年之前 回复

可能的,原因是enable不是同步即时的(即你说的非violate的),然后线程A就会看不到enable的变化,就一直不断的执行while代码

Evin7
Evin7 谢谢你的热情帮助,其实我的问题就是从这里来的,但是我觉得帖子中例子举得非常不合适,因为时序的问题也可以造成死循环的出现。我就是在怀疑这个帖子(对应的书是java并发编程实践,第33页左右)的正确性。它的原因并不能够充分说明我觉得例子(原因1:只是说某些时刻,不是永远;原因2:“导致一个线程过早的看到另外一个线程的写入操作完成之后的新值”,对我的问题没影响;原因3:我没有理解到)。
大约 8 年之前 回复
whg333
whg333 LZ建议看下这个:http://007007jing.iteye.com/blog/1522625
大约 8 年之前 回复
whg333
whg333 嗯,很可能一直都看不到,这取决于JVM什么时候把enable写入一个共享变量内存区域,violate是强制写入该区域的
大约 8 年之前 回复
Evin7
Evin7 那么就是说线程A有可能会永远看不到enable的变化?
大约 8 年之前 回复

建议你把java编程思想第21章看几遍 边看边实践 看到你懂为止 比任何回答都强

Evin7
Evin7 多谢,我回去再查一下。
大约 8 年之前 回复

如果你的2个线程是共享一个AAAAA对象的话,那是必定会退出循环的,2个线程共享的是相同的对象,同一个内存地址,线程B如果改变了enable的地址值,那么线程A的调用的methodAwhile循环条件就变成了false,那就退出了循环。

if (enable){
dosomething();
}

如果你这个dosomething显示执行methodB,然后将enable = true;
这样就可以实现线程A一直循环下去了

Evin7
Evin7 dosomething方法是与methodB完全无关的事情。
大约 8 年之前 回复
共15条数据 1 尾页
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐