我阅读了一些关于易变性线程缓存的文章,发现如果没有示例,它太简短了,所以初学者很难理解。
请帮助我理解以下程序,
public class Test {
int a = 0;
public static void main(String[] args) {
final Test t = new Test();
new Thread(new Runnable(){
public void run() {
try {
Thread.sleep(3000);
} catch (Exception e) {}
t.a = 10;
System.out.println("now t.a == 10");
}
}).start();
new Thread(new Runnable(){
public void run() {
while(t.a == 0) {}
System.out.println("Loop done: " + t.a);
}
}).start();
}
}
当我a
变量volatile
并运行我的程序时,它会在一段时间后停止但是当我将volatile
移到a
变量时,它会继续程序没有停止。
我对volatile的了解是“当变量被声明为volatile时,线程将直接读/写到变量内存而不是从本地线程缓存读/写。 如果没有声明为volatile,那么可以看到更新实际值的延迟。“
另外,根据我对刷新缓存副本的理解,我认为程序会在一段时间内停止但是为什么在上面的程序中它会继续运行而不是更新。
那么什么时候Thread引用它的本地缓存开始引用主副本或者用主副本值刷新它的值?
如果我理解错误,请纠正我。
请使用一些小代码段或链接向我解释。
答案 0 :(得分:8)
当变量被声明为volatile时,线程将直接读/写到变量内存,而不是从本地线程缓存读/写。如果没有声明为volatile,那么可以看到更新实际值的延迟。
首先,上述陈述是错误的。在机器代码级别上还有更多的现象与任何“线程局部变量高速缓存”无关。事实上,这个概念根本不适用。
为了给你一些特定的东西,JIT编译器将被允许转换你的代码
while(t.a == 0) {}
到
if (t.a == 0) while (true) {}
每当t.a
不是volatile
时。 Java内存模型允许将数据争用中访问的任何变量视为访问线程唯一存在的线程。由于此线程显然没有修改t.a
,因此可以将其值视为循环不变,并且不必重复检查... 永远。
答案 1 :(得分:2)
这可能是也可能不一定是发生的事情,但volatile
也阻止了编译器的某些重新排序。
例如你的代码
while(t.a == 0) {}
System.out.println("Loop done: " + t.a);
可以重新订购
if(t.a == 0){
while(true){
}
}
System.out.println("Loop done: " + t.a);
这称为吊装,完全合法。声明它不稳定将阻止这种排序。
答案 2 :(得分:0)
如果变量未声明为volatile,则无法从缓存或主副本中读取该变量是不可预测的。
缓存的大小有限,并且变量可能由于各种原因而被驱逐出去,就像占用缓存的其他变量一样。发生这种情况时,会读取主副本。
答案 3 :(得分:0)
标记变量volatile
确保在线程中可以看到对它的更改。
在你的代码中,第一个线程会更改a
的值,而其他线程会看到此更改并突然循环。
关于volatile变量的重要注意事项是,值可以在上次访问和当前访问之间发生变化,即使编译器知道其值未被更改。 (正如@Marko Topolnik所说) 这会阻止JIT编译器执行
之类的优化while(t.a == 0) {}
到
if (t.a == 0) while (true) {}
知道a
无法改变。
这个讲话对这些事情提供了非常好的解释。 Jeremy Menson's talk on Java Memory Model@Google