如果两个非同步线程将一个计数器递增 X 次,总结果是否可以小于 X?

If two unsynchronized threads increment a counter X times, can the total result be less than X?

我在一个紧密循环中有两个非同步线程,将全局变量递增 X 次 (x=100000)。

全局正确的最终值应该是 2*X,但由于它们是不同步的,所以它会更小,根据经验,它通常只是略高于 X

但是,在所有测试运行中,global 的值从未低于 X。

有没有可能最终结果小于x(小于100000)?

public class TestClass {
    static int global;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread( () -> {  for(int i=0; i < 100000; ++i) {     TestClass.global++;  }  });
        Thread t2 = new Thread( () -> { for(int i=0; i < 100000; ++i) {     TestClass.global++;  }  });
        t.start(); t2.start();
        t.join(); t2.join();
        System.out.println("global = " + global);
    }
}

想象一下以下场景:

  • 线程A从global
  • 读取初始值0
  • 线程 B 在 global
  • 上执行 99999 次更新
  • 线程 A 将 1 写入 global
  • 线程 B 从 global
  • 读取 1
  • 线程 A 在 global
  • 上执行剩余的 99999 次更新
  • 线程 B 将 2 写入 global

然后,两个线程都完成了,但结果值为 2,不是 2 * 100000,也不是 100000

请注意,上面的示例只是使用了错误的时序,没有让任何线程感知到其他线程乱序的读取或写入(这在没有同步的情况下是允许的),也没有丢失更新(这也会在这里被允许)。

换句话说,当 global 变量被声明为 volatile.

时,上面显示的场景甚至是可能的

推理读写及其可见性是一个常见的错误,但隐含地假设了线程代码执行的特定时间。但不能保证这些线程 运行 并排具有相似的指令时序。

但这可能仍会在您的测试场景中发生,因此它们不会揭示其他可能的行为。此外,某些合法行为可能永远不会发生在特定硬件或特定 JVM 实现上,而开发人员仍然必须对其负责。可能很好,优化器用 global += 100000 的等价物替换递增循环,在这样的测试中很少表现出中间值,但是在循环体中插入一些其他重要的操作可能会改变行为完全。