java - 访问变量时线程行为的差异

标签 java multithreading jvm thread-safety volatile

我正在阅读教程 https://www.youtube.com/watch?v=SC2jXxOPe5E了解 volatile 变量的工作原理并遇到了奇怪的行为。

对于以下代码片段

public class VolatileDemo {
    static boolean running = false;
    public static void main(String a[]) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!running) {
                }
                System.out.print("Started");
                while (running) {
                }
                System.out.print("Stopped");
            }
        });
        t.start();
        Thread.sleep(1000);
        running = true;
        System.out.print("Starting ");
        Thread.sleep(1000);
        running = false;
        System.out.print("Stopping");
    }
}

输出是:开始停止(可以通过视频理解)

但对于以下代码片段

public class VolatileDemo {
    static boolean running = false;
    public static void main(String a[]) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!running) {
                    System.out.print("Flag " + running);
                }
                System.out.print(" Started");
                while (running) {
                    System.out.print(" Flag " + running);
                }
                System.out.print(" Stopped");
            }
        });
        t.start();
        Thread.sleep(1000);
        running = true;
        System.out.print(" Starting");
        Thread.sleep(1000);
        running = false;
        System.out.print(" Stopping");
    }
}

输出为 Flag: false Started Started Flag: true Stopping Stopped(忽略输出)

我关心的是为什么线程能够在情况 2 中读取“running”的更新值?

编辑:两个片段之间的区别是在后面的情况下添加下面的语句

System.out.print("Flag " + running);

最佳答案

我认为了解 volatile 的用途很重要。

在具有多级缓存的多处理器系统上,对变量的更新可能需要一些时间才能到达主内存,因此到达其他线程,具体取决于延迟和硬件设计。代码基本上应该演示这种情况的发生,对运行变量的更改发生了更改,并且输出应该显示更改时间和线程实际启动时间之间的一些延迟。添加 volatile 关键字应该可以减少这种延迟,因为它会强制立即写入主内存,而不是在缓存决定时才执行。

请注意, volatile 并不使代码线程安全,它只是告诉 JVM 需要将变量直接写入主内存,绕过缓存硬件可能会执行的任何延迟写入方案。这也意味着该变量是从主内存中读取的,因此不会使用过时的数据。它是为了减少变量在一个线程中更新和在另一个线程中看到更新之间的延迟。这不是您经常需要的东西,应该谨慎使用,因为绕过缓存会对您的代码产生性能影响。

当您向线程代码中添加额外的指令时,您实际上显着降低了它轮询运行变量的速率。我想说,运行被更改和在主内存中更新之间的时间很可能非常短,比在控制台上输出所需的时间快得多(这比您想象的要长)。因此,您很可能不会看到您所期望的结果,除非 while 循环中的 boolean 求值恰好发生在极少数情况下。

与死锁不同,多处理的这种特殊属性很难在单台机器上演示。这种情况更有可能发生在 NUMA 系统架构(或集群)上,其中缓存到内存的更新延迟可能会更大。在单个系统上,变量在缓存中更新和写入主内存之间的时间非常短。

关于java - 访问变量时线程行为的差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57635655/

相关文章:

hadoop - Distcp - 容器运行超出物理内存限制

jvm - 如何获取Java8 Metaspace转储(不是堆转储)

java - 处理大量数据

java - 相对路径不起作用

.net - 高度并行的 F# 程序显示 CPU 利用率低

c - 在 C 中使用 GTK+ 单击按钮后 GUI 变得无响应

multithreading - 是否应该使用像 Akka 这样的基础设施来包装阻塞调用(到关系数据库)?

java - 修改 JVM 以序列化跨线程的文件访问

java - 从 Java 小程序打开 "byte array document"

java - Linux - Java 应用程序的启动画面