如果两个不同步的线程递增一个计数器 X 倍,则总结果是否可以小于 X?



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

全局的正确最终值应该是 2*X,但由于它们是不同步的,它会更少,根据经验,它通常只比 X 多一点

但是,在所有测试运行中,全局的值从未低于 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,这可能很好,在此类测试中很少显示中间值,但是在循环主体中插入其他一些重要的操作可能会完全改变行为。

最新更新