我正在尝试来自JCIP的示例,而且程序不应该有效,但即使我执行了20次它总是有效,这意味着ready
和number
变得可见,即使在这种情况下应该
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread implements Runnable {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
System.out.println(Runtime.getRuntime().availableProcessors());
//Number of Processor is 4 so 4+1 threads
new Thread(new ReaderThread()).start();
new Thread(new ReaderThread()).start();
new Thread(new ReaderThread()).start();
new Thread(new ReaderThread()).start();
new Thread(new ReaderThread()).start();
number = 42;
ready = true;
}
}
在我的机器上它始终打印
4 -- Number of Processors
42
42
42
42
42
According to Listing 3.1 of JCIP It should sometimes print 0 or should never terminate it also suggest that there is no gaurantee that ready and number written by main thread will be visible to reader thread
更新 在说明所有线程仍然相同的输出后,我在主线程中添加了1000ms睡眠。我知道程序已经坏了我希望它的行为方式
答案 0 :(得分:3)
此程序已被删除,因为ready
和number
应声明为volatile
。
由于ready
和number
是原始变量这一事实,对它们的操作是原子但是它不保证它们可见其他线程。
似乎调度程序在 main
之后运行线程,这就是他们看到number
和ready
被初始化的原因。但那只是一个安排
如果您添加例如sleep
中的main
以便影响调度程序,您会看到不同的结果
所以这本书是正确的,不能保证在单独的线程中运行的Runnable
是否会看到变量被更新,因为变量未被声明为volatile
。
<强>更新强>
这里的问题是由于缺少volatile
编译器,只需一次读取字段ready
,并在循环的每次执行中重用缓存值。
该计划本质上存在缺陷。并且通过线程问题,当您将应用程序部署到现场时,通常会出现问题....
来自JSL:
例如,在下面(破碎的)代码片段中,假设 this.done是一个非易失性布尔字段:
while(!this.done)
了Thread.sleep(1000);编译器可以自由阅读this.done字段一次,并重用 每次执行循环时的缓存值。这意味着 循环永远不会终止,即使其他线程改变了 this.done的价值。
答案 1 :(得分:1)
重要的是要记住,一个破碎的并发程序可能总是适用于JVM的选项,机器架构等的正确组合。这并不能使它成为一个好的程序,因为它可能会失败一个不同的背景:没有出现并发问题的事实并不意味着没有。
换句话说,您无法通过测试证明并发程序是正确的。
回到您的具体示例,我看到的行为与您描述的行为相同。但是,如果我删除while循环中的Thread.yield()
指令,则8个线程中的3个停止并打印42,但其余的不会,程序永远不会结束。