我从 Java Concurrency in Practice 中读到它,在线程中共享变量而不同步是很不好的。但是,对于下面仅具有一个读取线程和一个写入线程的一些示例,我无法在其中找到错误。从我的角度来看,以下程序的结果肯定会终止并打印42,因为ReaderThread只有在ready变为true时才能通过,这意味着数字是42。有人可以给我一些解释为什么我错了吗?
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;
}
}
答案 0 :(得分:3)
由于ready
不是volatile
,所以不能保证ReaderThread
会看到您的主线程已对其进行了更改。当您将ready
标记为易失性时,一个线程的所有写操作都会在其他线程中看到。
在线程之间进行通信时,您总是需要某种同步/可见性控制。无论是volatile
,显式使用synchronized
还是使用java.util.concurrent.*
类。
答案 1 :(得分:3)
在您的示例中,您不需要同步(例如,synchronized
)(尽管您确实需要volatile
,更多信息在下文中),因为读写boolean
和{{1} }变量始终是原子的。也就是说,当另一个线程出现并读取它时,通过写入int
(或boolean
)变量不能中途 。由一个线程写入的值总是在另一个线程可以读取它之前被完全写入。 (这是not true的非int
volatile
或double
变量;如果一个线程恰巧在另一个线程的中间读取,则完全有可能读取垃圾如果线程未标记为long
,则将它们写入long
或double
。)
但是您确实需要volatile
,因为每个线程可以拥有自己的变量副本,并且有可能长时间使用自己的副本。因此,您的阅读器线程完全有可能永远等待,因为它会不断重新读取自己的volatile
副本,即使您的主线程向其写入ready
,该副本仍会保留false
true
的副本。您的阅读器线程也有可能看到ready
变成ready
,但继续阅读自己的true
副本,因此打印0而不是42。
如果要修改不保证线程安全访问的对象的状态,则需要使用number
。例如,如果要添加到synchronized
或Map
。这是因为涉及多个操作,因此必须防止一个线程读取另一线程正在进行的半完成更改。
其他类(例如List
中的类)提供具有线程安全访问语义的类。