You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java数据竞争疑问:无同步布尔变量为何可能引发无限循环?

Java数据竞争与无限循环问题解析

问题背景

DZone上有这样的论断:当两个或更多线程在无同步的情况下访问同一个非final变量时会发生数据竞争。不使用同步可能导致修改对其他线程不可见,进而读取到过期数据,引发无限循环、数据结构损坏或计算错误等问题。文中给出如下代码,称其可能因读取线程永远无法感知写入线程的修改而陷入无限循环:

class Waiter implements Runnable {
  private boolean shouldFinish;

  void finish() { shouldFinish = true; }

  public void run() {
    long iteration = 0;
    while (!shouldFinish) {
      iteration++;
    }
    System.out.println("Finished after: " + iteration);
  }
}

class DataRace {

  public static void main(String[] args) throws InterruptedException {
    Waiter waiter = new Waiter();
    Thread waiterThread = new Thread(waiter);
    waiterThread.start();

    waiter.finish();
    waiterThread.join();
  }
}

疑问点

  • Java中boolean类型的读写属于原子操作,为啥还会出现无限循环?
  • 即便某次循环没感知到修改,下一次循环总该能看到变化吧?
  • 实际情况真的会无限循环吗?原因是读取线程永远不让渡CPU给写入线程吗?如果是这样,这还能被称为数据竞争吗?

解答

这段代码确实可能陷入无限循环,核心原因不是线程不让渡CPU,而是Java内存模型(JMM)的可见性规则和JIT编译器的优化,具体如下:

  1. 原子性≠可见性
    boolean的读写是原子操作没错,但原子性仅保证操作不会被拆分执行,不保证修改后的数值能立刻同步到主存并被其他线程看到。每个线程都有自己的工作内存,会将主存中的变量拷贝到本地缓存使用。写入线程调用finish()修改shouldFinish后,这个修改可能仅停留在自身的工作内存中,没有同步到主存;而读取线程则一直从自己的工作内存中读取旧值,永远看不到更新。

  2. JIT编译器的激进优化
    JIT编译器会对循环代码做优化,比如将while (!shouldFinish)中的变量读取直接缓存到寄存器中,不再每次循环都去主存重新读取。也就是说,代码会被优化成类似“如果第一次读取到shouldFinish为false,就直接进入无限循环,再也不检查主存中变量的最新值”,这直接导致读取线程永远感知不到写入线程的修改。

  3. 这确实属于数据竞争
    数据竞争的定义是:多个线程访问同一非final变量,至少有一个线程在执行写操作,且没有使用同步机制(如synchronized、volatile等)来协调这些访问。这里写入线程修改shouldFinish,读取线程循环读取该变量,完全符合数据竞争的条件,和线程是否让渡CPU无关——哪怕写入线程已经执行完finish()方法,读取线程仍可能因可见性问题看不到修改。

内容的提问来源于stack exchange,提问作者xpt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 11:36:04