[英]Do non-synchronized i++ overlap?
试图获得 Java 多线程基础知识我遇到了一个我无法理解的案例。 社区,请分享您的经验为什么会这样:我有一个 Runnable:
class ImproperStateWorker implements Runnable {
private int val = 0;
@Override
public void run() {
//Where are missing ticks?
for (int i = 0; i < 1000; i++) {
val++;
Thread.yield();
val++;
}
showDataState();
}
public void showDataState() {
System.out.print(Thread.currentThread() + " ; ");
System.out.println(val);
}
}
这是通过以下方式启动的:
public class ImproperState {
public static void main(String[] args) {
ImproperStateWorker worker = new ImproperStateWorker();
for (int i = 0; i < 2; i++) {
Thread thread = new Thread(worker);
thread.start();
}
}
}
我知道这个想法是通过使用synchronized() {...}
等使两个增量操作成为原子操作。 但我对以下内容感到困惑:为什么运行这两个 Runnables(不同步)不会产生 4000(每个任务 1000 x 2 增量)的一致结果? 不管上下文如何在两个任务之间切换,我希望这些任务每个都执行 2000 个增量,我不在乎顺序是什么。
然而,程序 output 给出了大约 3.5K。 我能想到的唯一想法是出现“缺失”增量,因为其中一些是同时产生的,因此从两个线程调用的val++
实际上将值增加了 1。但这是一个非常模糊的假设。 感谢您分享您的体验。
您的代码中有竞争条件。 考虑以下可能的交错:
val
→ 0
0
→ 1
val
← 1
val
→ 1
1
→ 2
val
← 2
val
→ 2
2
→ 3
val
← 3
val
→ 3
3
→ 4
val
← 4
val == 4
一切都很好。 但是,考虑这种同样可能的交错:
val
→ 0
val
→ 0
0
→ 1
0
→ 1
val
← 1
val
← 1
val
→ 1
val
→ 1
1
→ 2
1
→ 2
val
← 2
val
← 2
val == 2
哎呀!
在您的问题中编写的代码中,结果可以是2000
到4000
之间的任何值。
解决此问题的一种方法是将AtomicInteger
与AtomicInteger.getAndIncrement()
或AtomicInteger.incrementAndGet()
一起使用(在您的情况下,忽略返回值并不重要;后缀val++
的正确等效项是val.getAndIncrement()
) 代替,像这样(你只需要改变三个地方,不包括import
。):
import java.util.concurrent.atomic.AtomicInteger;
class FixedStateWorker implements Runnable {
private AtomicInteger val = new AtomicInteger();
// ↑↑↑↑↑↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
val.getAndIncrement();
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
Thread.yield();
val.getAndIncrement();
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
showDataState();
}
}
你有两个相互作用的问题。 一个是已经提到的竞争条件,另一个是共享变量更改线程之间缺乏可见性。 您必须使用一些并发习语来解决这两种情况。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.