不同步即可共享变量

时间:2018-08-03 09:04:54

标签: java concurrency

我从 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;
    } 
}

2 个答案:

答案 0 :(得分:3)

由于ready不是volatile,所以不能保证ReaderThread会看到您的主线程已对其进行了更改。当您将ready标记为易失性时,一个线程的所有写操作都会在其他线程中看到。

在线程之间进行通信时,您总是需要某种同步/可见性控制。无论是volatile,显式使用synchronized还是使用java.util.concurrent.*类。

答案 1 :(得分:3)

在您的示例中,您不需要同步(例如,synchronized)(尽管您确实需要volatile,更多信息在下文中),因为读写boolean和{{1} }变量始终是原子的。也就是说,当另一个线程出现并读取它时,通过写入int(或boolean)变量不能中途 。由一个线程写入的值总是在另一个线程可以读取它之前被完全写入。 (这是not true的非int volatiledouble变量;如果一个线程恰巧在另一个线程的中间读取,则完全有可能读取垃圾如果线程未标记为long,则将它们写入longdouble。)

但是您确实需要volatile,因为每个线程可以拥有自己的变量副本,并且有可能长时间使用自己的副本。因此,您的阅读器线程完全有可能永远等待,因为它会不断重新读取自己的volatile副本,即使您的主线程向其写入ready,该副本仍会保留false true的副本。您的阅读器线程也有可能看到ready变成ready,但继续阅读自己的true副本,因此打印0而不是42。

如果要修改不保证线程安全访问的对象的状态,则需要使用number。例如,如果要添加到synchronizedMap。这是因为涉及多个操作,因此必须防止一个线程读取另一线程正在进行的半完成更改。

其他类(例如List中的类)提供具有线程安全访问语义的类。