如果两个非同步线程将一个计数器递增 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
的等价物替换递增循环,在这样的测试中很少表现出中间值,但是在循环体中插入一些其他重要的操作可能会改变行为完全。
我在一个紧密循环中有两个非同步线程,将全局变量递增 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
读取初始值 - 线程 B 在
global
上执行 99999 次更新
- 线程 A 将
1
写入global
- 线程 B 从
global
读取 - 线程 A 在
global
上执行剩余的 99999 次更新
- 线程 B 将
2
写入global
0
1
然后,两个线程都完成了,但结果值为 2
,不是 2 * 100000
,也不是 100000
。
请注意,上面的示例只是使用了错误的时序,没有让任何线程感知到其他线程乱序的读取或写入(这在没有同步的情况下是允许的),也没有丢失更新(这也会在这里被允许)。
换句话说,当 global
变量被声明为 volatile
.
推理读写及其可见性是一个常见的错误,但隐含地假设了线程代码执行的特定时间。但不能保证这些线程 运行 并排具有相似的指令时序。
但这可能仍会在您的测试场景中发生,因此它们不会揭示其他可能的行为。此外,某些合法行为可能永远不会发生在特定硬件或特定 JVM 实现上,而开发人员仍然必须对其负责。可能很好,优化器用 global += 100000
的等价物替换递增循环,在这样的测试中很少表现出中间值,但是在循环体中插入一些其他重要的操作可能会改变行为完全。