考虑一个简单的名叫Counter的类。
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
Counter类被设计成如下:每次调用increment方法,都会使c加一,每次调用decrement方法,都会使c减一。但是,如果一个Counter对象被多个线程引用,多个线程之间的混淆会导致功能的失常。
当两个运行于不同线程,却对同样的数据处理的操作出现时,混淆就发生了。这意味着有多个子步骤组成的操作,它们的顺序重叠了。
看上去,对Counter对象的操作不可能会重叠,因为两个对变量c的操作都是单句的简单陈述。但是,即使是简单的语句,虚拟机也会把它翻译成几个步骤。比如,C++这句语句,会被分成三个步骤:
- 获取当前c的值。
- 将c的值加1。
- 将增加过的c存回到内存中。
c--的操作亦然。
假设线程A调用了increment方法,而几乎同时线程B调用了decrement方法。如果c的初始值是0,两个线程混淆的操作可能是这样的顺序:
- 线程A:获取c。
- 线程B:获取c。
- 线程A:对c进行加一操作,结果是1。
- 线程B:对c进行减一操作,结果是-1。
- 线程A:把结果存回c中,c现在的值是1。
- 线程B:把结果存回c中,c现在的值是-1。
线程A的结果丢失了,被线程B覆盖写了。上面的这种混淆,只是一种可能性。在不同的情况下,也可能会变成线程B的结果丢失了,或者也可能没有错误。由于这是无法预料的,线程混淆的bug很难找到和修复。