java - while(true) 使cpu无法读取共享变量的最新值

标签 java multithreading volatile cpu-cache

我有这个 java 代码:

class FlagChangeThread implements Runnable {
    private boolean flag = false;
    public boolean isFlag() {return flag;}
    public void run() {
        try {Thread.sleep(300);} catch (Exception e) {}
        // change value of flag to true
        flag=true;
        System.out.println("FlagChangeThread flag="+flag);
    }
}
public class WhileLoop {
    public static void main(String[] args) {
        FlagChangeThread fct = new FlagChangeThread();
        new Thread(fct).start();
        while (true){
            // but fct.isFlag() always get false
            boolean flag = fct.isFlag();
            if(flag){
                System.out.println("WhileLoop flag="+flag);
                break;
            }
        }
    }
}

当我运行这段代码时,整个程序只打印下面的消息并永远卡住:

FlagChangeThread flag=true

但是当我在主线程的 while 循环中添加一些 sleep 时间时,就像这样:

class FlagChangeThread implements Runnable {
    private boolean flag = false;
    public boolean isFlag() {return flag;}
    public void run() {
        try {Thread.sleep(300);} catch (Exception e) {}
        // change value of flag to true
        flag=true;
        System.out.println("FlagChangeThread ="+flag);
    }
}
public class WhileLoop {
    public static void main(String[] args) throws InterruptedException {
        FlagChangeThread fct = new FlagChangeThread();
        new Thread(fct).start();
        while (true){
            Thread.sleep(1);
            boolean flag = fct.isFlag();
            if(flag){
                System.out.println("WhileLoop flag="+flag);
                break;
            }
        }
    }
}

再次运行,整个程序打印如下信息并正常退出:

FlagChangeThread =true
WhileLoop flag=true

我知道将标志变量声明为 volatile 也可以解决这个问题,因为当标志被更改时,它将被写回主内存并且标志变量的其他 cpu 缓存行无效。

但是,我有这样的困惑:

  1. 为什么主线程while循环中的fct.isFlag()不 hibernate 就取不到最新值?

  2. flag 变为true 后,即使它现在在线程的工作内存中,但在未来的某个时刻,它最终会写回主内存。为什么主线程调用fct.isFlag()读取不到这个更新后的值?不是每次调用fct.isFlag()时都从主内存中获取flag值并复制到主线程的工作内存中吗?

谁能帮帮我?

最佳答案

原因是邪恶的硬币。

此处相关的规范是 Java 内存模型 (JMM)。

JMM 具有以下方面:

任何线程都可以自由创建或不创建变量的本地缓存副本,并且可以根据自己的想法和月相(如果需要)引用或不引用该副本。

换句话说,线程抛硬币来决定做什么。这是邪恶的,因为它不会以大约 50/50 的比例翻转正面/反面。假设它抛硬币来惹你:它工作了一个小时左右,然后当你明天早上再次开始工作时它突然开始失败,你不知道发生了什么。

因此,在您的某些调用中,您正在查看的 boolean 字段正在获取缓存副本。

换句话说:

如果多个线程正在处理同一个字段,除非您建立 HB/HA,否则您的应用程序的行为是不确定的。

它以这种奇怪的方式工作的原因是速度:任何其他定义都意味着 JVM 运行代码的速度必须慢几个数量级。

解决方案是建立 HB/HA:Happens-Before/Happens-After 关系。

HB/HA 是这样工作的:如果两行代码之间存在 HB/HA 关系,那么就不可能观察到运行 Happens-Before 行之前时的状态,来自 Happens-After 行。换句话说,如果一个字段在 HB 行之前的值为“5”,在“HB”行之后的值为“7”,则 HA 行不可能观察到 5。它可以观察到 7,或之后发生的一些更新。

规范列出了一堆建立 HB/HA 的东西:

  • volatile 的任何访问权限领域。您现在就可以尝试:将那个字段设置为 volatile ,它会“修复”它。
  • 退出一个synchronized(x) block 是 HB 与输入 synchronized(theSameX)阻塞在另一个线程中(当然,如果进入那个 block 实际上发生在之后)。
  • t.start()方法是 HB 相对于 run() 中的第一行你开始的话题。
  • 在一个线程中,任何先于其他任何代码运行的代码行都是 HB(这是微不足道的情况)。

JVM 中的一些东西使用这个东西。

提示:

  • 通常,使用来自 java.util.concurrent 的东西包。
  • 尽量避免与来自不同线程的同一字段进行交互。
  • 考虑数据库或消息队列或其他对线程间通信规则不那么挑剔的系统。
  • 如果写入其他线程应该读取的字段,您必须考虑 HB/HA。
  • 这些都不是保证。 您可以编写有问题的代码,尽管今天、明天和下周在生产机器上通过了所有测试,但在下个月向大客户提供重要演示时却失败了。因此,这里有龙:如果你搞砸了,你可能不知道,直到 bug 强加给你的成本膨胀到失控为止。因此,除非你真的、真的、真的知道你在做什么,否则请避免这些东西。

关于java - while(true) 使cpu无法读取共享变量的最新值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68785125/

相关文章:

java - RESTful Web 服务在本地主机服务器中运行,但不在生产服务器中运行

java - Kotlin找不到符号Function1构建错误

java - Android/Java - 如何检查 OutputStream 何时完成写入字节

multithreading - 多线程和并行编程的区别?

java - wait() 是否需要局部变量同步

c# - 在定时器的回调方法中抛出异常

c++ - 这里是否需要 volatile

java - 取 3 个整数,创建一个日期

java - java中的可变标识符

C++ const 与 volatile