竞态条件仅在多个线程都在访问同一个资源,并且一到多个线程写入该资源时发生。如果多个线程只是读同一个资源,竞态条件就不会发生。
我们可以通过让共享对象变成不可变的,确保线程之间共享的对象永远不会被任何线程更新,从而实现线程安全。如下是一个示例:
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
实例,并把相加操作的结果传入新实例的构造器,而不是直接把值加给原来的 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
类在内部使用了一个不可变的对象,但是它本身不是不可变的,因此也不是线程安全的。换句话说:ImmutableValue
类是线程安全的,但是它的使用不是。这是当试着通过不可变性实现线程安全时要记住的事情。
为让 Calculator
类变成线程安全的,可以用 synchronized
声明 getValue()
、setValue()
和 add()
方法。这样做就会见效。