竞态条件仅在多个线程都在访问同一个资源,并且一到多个线程写入该资源时发生。如果多个线程只是读同一个资源,竞态条件就不会发生。

我们可以通过让共享对象变成不可变的,确保线程之间共享的对象永远不会被任何线程更新,从而实现线程安全。如下是一个示例:

  1. public class ImmutableValue{
  2. private int value = 0;
  3. public ImmutableValue(int value){
  4. this.value = value;
  5. }
  6. public int getValue(){
  7. return this.value;
  8. }
  9. }

请注意,ImmutableValue 实例的值是如何传递到构造器的。还请注意这里没有 setter 方法,所以一旦 ImmutableValue 实例被创建,我们就没法修改它的值。于是它就成了不可变的。不过,我们可以用 getValue() 方法读它。

如果需要在 ImmutableValue 实例上执行某个操作,可以通过返回一个新的实例来实现,这个新实例带有该操作的结果值。如下是一个相加操作的示例:

  1. public class ImmutableValue{
  2. private int value = 0;
  3. public ImmutableValue(int value){
  4. this.value = value;
  5. }
  6. public int getValue(){
  7. return this.value;
  8. }
  9. public ImmutableValue add(int valueToAdd){
  10. return new ImmutableValue(this.value + valueToAdd);
  11. }
  12. }

请注意,这里 add() 方法返回一个新的 ImmutableValue 实例,并把相加操作的结果传入新实例的构造器,而不是直接把值加给原来的 ImmutableValue 实例本身。

引用不是线程安全的!

重要的是要记住,即使对象是不可变的,因而是线程安全的,这个对象的引用也可能不是线程安全的。请看下面这个例子:

  1. public class Calculator{
  2. private ImmutableValue currentValue = null;
  3. public ImmutableValue getValue(){
  4. return currentValue;
  5. }
  6. public void setValue(ImmutableValue newValue){
  7. this.currentValue = newValue;
  8. }
  9. public void add(int newValue){
  10. this.currentValue = this.currentValue.add(newValue);
  11. }
  12. }

Calculator 持有对 ImmutableValue 实例的一个引用。通过 setValue()add() 方法都可以修改该引用。因此,即使 Calculator 类在内部使用了一个不可变的对象,但是它本身不是不可变的,因此也不是线程安全的。换句话说:ImmutableValue 类是线程安全的,但是它的使用不是。这是当试着通过不可变性实现线程安全时要记住的事情。

为让 Calculator 类变成线程安全的,可以用 synchronized 声明 getValue()setValue()add() 方法。这样做就会见效。