本文翻译自http://tutorials.jenkov.com/java-concurrency/thread-safety-and-immutability.html,人工翻译,仅供学习交流。
线程安全及不可变性
只有当多个线程访问同一资源时才会出现竞争条件,一个或多个线程写入资源。多个线程读取相同的资源竞态条件不会发生。我们可以通过使共享对象不可变确保线程之间共享的对象不会被任何线程,更新因此线程是安全的。下面是一个例子:
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类在内部使用了不可变对象,它本身不是不可变的,因此不是线程安全的。换句话说ImmutableValue类是线程安全的,但使用它却并非如此。当试图通过不变性来实现线程安全时,要记住这一点。
使Calculator类线程安全,你可以声明getValue(), setValue()和add()方法是同步的。这样就可以了。