# 线程干扰
考虑一个简单的类
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Counter 被设计成使得每次调用 increment
将添加 1 到 c,并且每次调用 decrement
将从 c-1。
然而,如果一个 Counter
对象被多个线程引用,则线程之间的干扰可能会阻止这样的预期。
干扰发生在两个操作,运行在不同的线程,但作用与相同的数据。而且是交错调用。这以为者这两个操作由多个步骤组成,并且步骤序列重叠。
Counter 由于这两个操作都是单一的简单语句,因此可能不会对实例进行交错操作。然而,即使简单的语句也可以转换为虚拟机的多个步骤。 我们不会检查虚拟机所采取的具体步骤,只要知道单个表达式 c++ 可以分解成以下三个步骤:
- 检索当前值 c
- 将检索到的值增加 1
- 将增加的值存储回来 c
表达式 c-- 可以以相同的方式分解,除了第二步是减少而不是增加
假设线程 A 调用 increment
的同一时间,线程 B 调用 decrement
。如果初始值 c = 0。他们的交错操作可能遵循以下顺序:
- 线程 A:检索 c
- 线程 B:检索 c
- 线程 A:增加检索值;结果是 1
- 线程 B:减去检索值;结果是 -1
- 线程 A:将结果存储在 c 中,c 现在是 1
- 线程 B:将结果存储在 c 中,c 现在是 -1
线程 A 的结构丢失,被线程 B 覆盖了。这种特定的交错只是一种可能性。在不同的情况下,线程 B 的结果可能会丢失, 或则完全没有错误。因为他们是不可预测的,线程干扰的 bug 可能难以检测和修复。