【问题标题】:Difference in thread behaviour while accessing variable访问变量时线程行为的差异
【发布时间】:2019-08-24 06:30:49
【问题描述】:

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

对于下面的代码sn -p

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");
    }
}

输出为:Starting Stopping(通过视频可以理解)

但是对于下面的代码sn-p

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 Starting Started Flag: true Stopping Stopped(忽略输出)

我关心的是为什么线程能够在案例 2 中读取 'running' 的更新值?

编辑:这两个sn-ps的区别是在后面的情况下添加了下面的语句

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

【问题讨论】:

  • 您的代码 sn-ps 似乎相同。你能强调一下不同之处吗?
  • 你的逻辑错了。使变量 volatile 允许带来内存可见性保证,并使代码线程安全。这并不意味着不使用 volatile 可以保证内存不可见。就像:过马路前看两边,确保你不会被车撞到。这并不意味着如果您在过马路前不看一眼,就一定会被车撞到。这样做是不安全的,但不能保证你会发生事故。
  • @JBNizet 很好的比喻

标签: java multithreading jvm thread-safety volatile


【解决方案1】:

我认为了解 volatile 的目的是什么很重要。

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

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

当您向线程代码添加额外指令时,您有效地显着降低了它轮询运行变量的速度。我会说运行更改和在主内存中更新之间的时间很可能非常短,比在控制台上输出所需的时间要快得多(这比您想象的要长)。因此,您很可能不会看到您期望的结果,除非在 while 循环中的布尔计算发生在完全正确的时刻这一极少数情况下。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-03
    • 2012-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多