非同步 i++ 是否重叠?
Do non-synchronized i++ overlap?
试图获得 Java 多线程基础知识我 运行 遇到了一个我无法理解的案例。社区,请分享您的经验,为什么会这样:
我有一个可运行的:
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() {...}
使两个增量操作成为原子操作,依此类推。
但我对以下内容感到困惑:为什么 运行ning 这两个 Runnable(不同步)不会产生 4000(每个任务 1000 x 2 增量)的一致结果?无论两个任务之间的上下文如何切换,我都希望这些任务每个执行 2000 个增量,我不关心顺序是什么。
但是,程序输出给出了 ~3.5K。
我能想到的唯一想法是 "missing" 增量出现,因为其中一些增量是同时进行的,因此从两个线程调用的 val++
实际上将值增加 1。但这是一个非常模糊的假设。感谢您分享您的经验。
您的代码中存在竞争条件。考虑以下可能的交错:
- 线程 1:读取
val
→ 0
- 线程 1:递增
0
→ 1
- 线程 1:写入
val
← 1
- 线程 1:读取
val
→ 1
- 线程 1:递增
1
→ 2
- 线程 1:写入
val
← 2
- 线程 2:读取
val
→ 2
- 线程 2:递增
2
→ 3
- 线程 2:写入
val
← 3
- 线程 2:读取
val
→ 3
- 线程 2:递增
3
→ 4
- 线程 2:写入
val
← 4
- 结束状态:
val == 4
一切都很好。但是,考虑这种同样可能的交错:
- 线程 1:读取
val
→ 0
- 线程 2:读取
val
→ 0
- 线程 1:递增
0
→ 1
- 线程 2:递增
0
→ 1
- 线程 1:写入
val
← 1
- 线程 2:写入
val
← 1
- 线程 1:读取
val
→ 1
- 线程 2:读取
val
→ 1
- 线程 1:递增
1
→ 2
- 线程 2:递增
1
→ 2
- 线程 1:写入
val
← 2
- 线程 2:写入
val
← 2
- 结束状态:
val == 2
糟糕!
在您问题中所写的代码中,结果可以是 2000
和 4000
之间的任何值。
解决此问题的一种方法是使用 AtomicInteger
with AtomicInteger.getAndIncrement()
or AtomicInteger.incrementAndGet()
(在您的情况下,哪个无关紧要,因为您忽略了 return 值;正确等同于后缀 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();
}
}
您有两个相互作用的问题。一个是已经提到的竞争条件,另一个是共享变量更改的线程之间缺乏可见性。您必须使用一些并发习惯用法来解决这两种情况。
试图获得 Java 多线程基础知识我 运行 遇到了一个我无法理解的案例。社区,请分享您的经验,为什么会这样: 我有一个可运行的:
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() {...}
使两个增量操作成为原子操作,依此类推。
但我对以下内容感到困惑:为什么 运行ning 这两个 Runnable(不同步)不会产生 4000(每个任务 1000 x 2 增量)的一致结果?无论两个任务之间的上下文如何切换,我都希望这些任务每个执行 2000 个增量,我不关心顺序是什么。
但是,程序输出给出了 ~3.5K。
我能想到的唯一想法是 "missing" 增量出现,因为其中一些增量是同时进行的,因此从两个线程调用的 val++
实际上将值增加 1。但这是一个非常模糊的假设。感谢您分享您的经验。
您的代码中存在竞争条件。考虑以下可能的交错:
- 线程 1:读取
val
→0
- 线程 1:递增
0
→1
- 线程 1:写入
val
←1
- 线程 1:读取
val
→1
- 线程 1:递增
1
→2
- 线程 1:写入
val
←2
- 线程 2:读取
val
→2
- 线程 2:递增
2
→3
- 线程 2:写入
val
←3
- 线程 2:读取
val
→3
- 线程 2:递增
3
→4
- 线程 2:写入
val
←4
- 结束状态:
val == 4
一切都很好。但是,考虑这种同样可能的交错:
- 线程 1:读取
val
→0
- 线程 2:读取
val
→0
- 线程 1:递增
0
→1
- 线程 2:递增
0
→1
- 线程 1:写入
val
←1
- 线程 2:写入
val
←1
- 线程 1:读取
val
→1
- 线程 2:读取
val
→1
- 线程 1:递增
1
→2
- 线程 2:递增
1
→2
- 线程 1:写入
val
←2
- 线程 2:写入
val
←2
- 结束状态:
val == 2
糟糕!
在您问题中所写的代码中,结果可以是 2000
和 4000
之间的任何值。
解决此问题的一种方法是使用 AtomicInteger
with AtomicInteger.getAndIncrement()
or AtomicInteger.incrementAndGet()
(在您的情况下,哪个无关紧要,因为您忽略了 return 值;正确等同于后缀 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();
}
}
您有两个相互作用的问题。一个是已经提到的竞争条件,另一个是共享变量更改的线程之间缺乏可见性。您必须使用一些并发习惯用法来解决这两种情况。