如何演示 Java 多线程可见性问题?

2022-09-01 07:47:56

如果从多个线程访问 Java 中的变量,则必须确保安全地发布它们。这通常意味着使用 或 。synchronizedvolatile

我得到的印象是,我的一些同事并不认真对待这个问题,因为他们“以前从未听说过,他们的程序已经工作了很多年”。volatile

所以我的问题是:

有人可以提供一个示例Java程序/代码片段,可靠地显示数据可见性问题。

我认为运行一个程序并看到意外的NPE或陈旧的变量值将比无法证明的理论解释更有帮助。

非常感谢您的帮助!

更新:只是为了再次强调这一点。我读过Java Concurreny in Practice,并知道理论上存在可见性问题的例子。我正在寻找的是一种实际展示它们的方法。我不确定,这实际上是可能的,但也许有一个jvm配置或类似的东西允许它。


答案 1

通过删除操作来修改此处的示例,我提出了一个在我的环境中始终失败的示例(线程永远不会停止运行)。

// Java environment:
// java version "1.6.0_0"
// OpenJDK Runtime Environment (IcedTea6 1.6.1) (6b16-1.6.1-3ubuntu3)
// OpenJDK 64-Bit Server VM (build 14.0-b16, mixed mode)
public class Test2 extends Thread {
    boolean keepRunning = true;
    public static void main(String[] args) throws InterruptedException {
        Test2 t = new Test2();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println(System.currentTimeMillis() + ": keepRunning is false");
    }
    public void run() {
        while (keepRunning) 
        {}
    }
}

请注意,这种类型的问题非常依赖于编译器/运行时/系统。特别是,编译器可以确定添加指令以从内存中读取变量,即使它不是易失性的 - 因此代码将起作用--,vm和jit可以优化从内存中读取并仅使用寄存器,甚至处理器可以重新排序指令 - 这不会影响这种情况,但在其他多线程情况下,如果修改了多个变量,它可能会影响其他线程的感知状态。


答案 2

为强调使用易失性的重要性而提出的最常见的例子是这个例子:while(keepRunning)

public class Test extends Thread {

    boolean keepRunning = true;

    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println(System.currentTimeMillis() + ": keepRunning is false");
    }

    public void run() {
        while (keepRunning) 
            System.out.println(System.currentTimeMillis() + ": " + keepRunning);
    }
}

由于可以(有效地)保存在运行 while-loop 的线程的缓存中,因此该程序可能会在设置为 false 后很长一段时间内打印“true”。keepRunningkeepRunningkeepRunning

但请注意没有可靠的方法来公开竞争条件。(请参阅我的其他答案。此示例可能在某些情况下在硬件/操作系统/jvm的某些组合上公开它。