public class NoVisibility{
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread{
public void run(){
while(!ready){
Thread.yield();
}
System.out.println(number);
}
}
public static void main(String[] args){
new ReaderThread().start();
number = 42;
ready = true;
}
}
上代码,这是并发编程实践里面的一个例子(清单 3.1 ),然后这段代码可能出现这种情况:可能一直保持循环。“因为对于读线程来说, ready 的值可能永远不可见”。我就纳闷了,怎么会永远不可见?你说一阵子不可见我能理解,真的会永远不可见吗?
1
doing 2016-05-26 11:35:05 +08:00
每个线程有自己单独的工作内存,操作 ready 变量 会把 ready 变量从主内存 复制到自己的工作内存中去操作。操作完后,会写回主内存。
在这个过程,可能就会不可见。 A 线程改了 ready 变量的值,但还没写回主内存。或者,新的 B 线程还没重新去主内存获取 ready 变量。 我是这样理解的。 |
2
honam OP @doing 感谢你的回答,“ A 线程改了 ready 变量的值,但还没写回主内存。或者,新的 B 线程还没重新去主内存获取 ready 变量。 ”是会这样,但是终究会写回内存 或者 会去主内存获取新值到工作内存吧?这两个操作难道都有可能会被终止掉?
|
3
incompatible 2016-05-26 12:04:46 +08:00 via iPhone
@honam 这个循环中的 ready 在编译后似乎会被优化成局部产量,初始化一次后就不变了,永远拿不到其他线程修改后的值。
|
4
honam OP @incompatible 谢谢回答,顺求相关资料,书里面一句带过没说原因好头痛。
|
5
incompatible 2016-05-26 12:26:34 +08:00 via iPhone
@honam 周志明「深入理解 Java 虚拟机」,里面有一整章是讲这个的。
|
6
incompatible 2016-05-26 12:30:09 +08:00 via iPhone
@honam 我上面说编译期被优化不是主要原因。主要原因还是 1 楼说的那样。 A 和 B 操作之间没有 Barrier ,所以 B 读不到 A 的更改。给 ready 加了 volatile 后就产生了一个 Barrier , B 就可以读到了。
|
7
honam OP @incompatible 我再问一个 volatile 的问题,如果变量不加这个修饰关键字,这个变量是不是不会在某个线程的工作内存失效,或者说,不会去主内存里面拿新的值?如果是我就明白了,如果不是,那还是回到我回答 1 楼的问题。。。
|
8
hadixlin 2016-05-26 12:57:44 +08:00
@honam 这个答案是不确定的,依赖 JVM 的具体实现.但是 volatile 的语义是虚拟机规范里面写明的,所有的 JVM 都必须实现该语义.
所以不用纠结变量什么时候去主存拿新值,只要知道没有 volatile 或者其他内存屏障,被多线程共享的变量的值是不可靠的. |
9
anexplore 2016-05-26 13:21:45 +08:00
我的考虑是,在 run()中 while(!ready)并没有改变 ready 的值,那么编译器是否会直接将其优化成 tmp = !ready; while(tmp),从而导致其一直运行
|
10
SoloCompany 2016-05-27 02:07:28 +08:00 1
没有 violate 也没有 synchronized 当然有可能永远看不见变化
但具体行为依赖于 jvm 实现而不是编译器 在真正的多核环境下并且每个核心都有独立缓存的执行条件下, ReaderThread 线程执行的工作区内存有可能永远都得不到更新 |