为什么下面的代码可以停止线程1?读取volatile变量是否会影响线程缓存中其他非volatile变量的值?当线程1读取“s”时,它还会重新加载新值“run”?
public class Vol {
boolean run = true;
volatile int s = 1;
public static void main(String[] args) throws InterruptedException {
Vol v = new Vol();
//thread 1
new Thread(() ->{
while (v.run) {
//with this, thread 1 can be shutdown
int a = v.s;
}
}).start();
//thread 2
new Thread(() ->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
v.run = false;
System.out.println("set run false");
}).start();
}
}
1条答案
按热度按时间uqcuzwp81#
java内存模型(jmm)定义了一些保证。除了这些保证之外,虚拟机还可以自由地做任何他们想做的事情。所以,通常情况下,对这样一个问题的答案是肯定的:“有没有可能在这里刷新缓存?”因为你应该问一些更绝对的问题:“有没有保证在这里刷新缓存?”这是一个更有趣的问题。
jmm基于“发生在之前/之后”的概念。jmm定义了一些特定的场景,在这些场景中jmm将保证代码的执行,从而使您观察到的内容与某一行发生在另一行之前的想法相匹配。对于没有建立hb/ha关系的所有java代码行,jvm的行为可能会导致您观察到一行在另一行之前运行,或者一行在另一行之后运行,甚至是一种奇怪的混合,其中部分是可观察的,而其他部分不是。
换言之,将其视为一枚邪恶的硬币:如果没有hb/ha,jvm会为每次交互都抛出一枚硬币(一个线程所做的更改,另一个线程能观察到吗?没有hb/ha,邪恶的硬币就会被翻转)。这是邪恶的,因为几率不是50/50,它只是在捣乱你:在开发和为期一周的测试期间,它每次都成功了,就在销售人员给大客户演示时,它失败了。
没有一个简单的方法来测试投币是否发生了。然而,你的任务是确保邪恶的硬币永远不会被翻转。这是很困难的,所以在编写多个线程读写同一字段的代码时,应该非常小心。
volatile
access确实建立了hb/ha关系。然而,很难知道朝哪个方向发展。给定此代码:
那么你可以保证:
Augh!
永远不会打印。这是因为如果将x读作20,hb/ha关系保证线程b中的行在a的第二行之后运行,因此保证观察到a在第二行上或之前所做的一切。但是,你绝对不能保证x在这里是20。只是,如果是的话,那么线程a对y所做的更改也可以保证被观察到,即使y不是volatile
.但是,在本规范中:
那么jvm就完全免费了,邪恶的投币正在发生!jvm可以自由打印
AUGH!
每一次,或从未,或有时,或只有在满月的时候。没有hb/ha,因此vm不能保证可观测性,也就没有意义了。jvm实现允许线程b观察对的更改是可以接受的y
但不是对x
即使线程实际按顺序运行代码这一简化但错误的概念表明这是不可能的。基本上,如果没有hb/ha,所有的赌注都是失败的。在代码段中,线程a正在读取volatile变量
s
,但这是唯一与s
可用;线b从不接触s。因此,此代码被破坏,邪恶的硬币翻转正在发生jvm可以按如下方式自由工作:线程2集
run
今天是假的。线程1仍然运行了3天,然后,似乎在一个完全任意的时间提示你什么都不知道,突然之间,它终于观察到run
现在是假的。或者,线程1在线程2设置之后立即停止
run
错误的。这取决于你的操作系统,cpu,winamp播放的是哪首歌,月亮的相位,还有蝴蝶是否拍动翅膀。没有办法知道,也没有(简单的)方法来测试你在这里搞砸了。