Jakob Jenkov 2014-07-18
竞态条件(Race conditions) 只有在多个线程访问同一资源,并且其中至少有一个线程对该资源进行写操作时才会发生。如果多个线程只是读取同一个资源,则不会出现竞态条件。
我们可以通过使线程间共享的对象不可变(immutable),从而确保这些对象永远不会被任何线程修改,进而实现线程安全。下面是一个示例:
public class ImmutableValue {
private int value = 0;
public ImmutableValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
注意,ImmutableValue 实例的值是在构造函数中传入的,并且没有提供 setter 方法。一旦创建了 ImmutableValue 的实例,其值就无法再被更改——它是不可变的。不过,你可以通过 getValue() 方法来读取它的值。
如果你需要对 ImmutableValue 实例执行某些操作,可以通过返回一个包含操作结果的新实例来实现。以下是一个加法操作的示例:
public class ImmutableValue {
private int value = 0;
public ImmutableValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
public ImmutableValue add(int valueToAdd) {
return new ImmutableValue(this.value + valueToAdd);
}
}
注意,add() 方法返回的是一个新的 ImmutableValue 实例,而不是直接修改当前对象的值。
引用本身不是线程安全的!
需要特别注意的是:即使一个对象是不可变的(因此是线程安全的),指向该对象的引用本身却未必是线程安全的。请看下面这个例子:
public class Calculator {
private ImmutableValue currentValue = null;
public ImmutableValue getValue() {
return currentValue;
}
public void setValue(ImmutableValue newValue) {
this.currentValue = newValue;
}
public void add(int newValue) {
this.currentValue = this.currentValue.add(newValue);
}
}
Calculator 类持有一个 ImmutableValue 实例的引用。注意,通过 setValue() 和 add() 方法都可以改变这个引用所指向的对象。因此,尽管 Calculator 内部使用的是不可变对象,但 Calculator 本身并不是不可变的,也就不是线程安全的。
换句话说:ImmutableValue 类是线程安全的,但它的使用方式可能不是线程安全的。这一点在试图通过不可变性来实现线程安全时必须牢记。
要让 Calculator 类变成线程安全的,可以将 getValue()、setValue() 和 add() 方法声明为 synchronized。这样就能解决问题了。