
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 stati 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 没有 violate 也没有 synchronized 当然有可能永远看不见变化 但具体行为依赖于 jvm 实现而不是编译器 在真正的多核环境下并且每个核心都有独立缓存的执行条件下, ReaderThread 线程执行的工作区内存有可能永远都得不到更新 |