本文内容大多基于官方文档和网上前辈经验总结,经过个人实践加以整理积累,仅供参考。
线程安全是指允许多个线程同时执行的代码
1 线程安全与共享资源
(1) 基础类型的局部变量
局部变量存储在线程自己的栈中,永远不可能被多个线程共享,所以基础类型的局部变量是线程安全的。
(2) 对象引用类型的局部变量
局部变量引用的对象没有存储在线程自己的栈中,所有的对象都存在共享堆中,如果此对象不会被其它方法使用,也不会被非局部变量引用,那么就是线程安全的。
示例:
public void method1() {
CustomObject object = new CustomObject();
object.customMethod();
method2(object);
}
public void method2(CustomObject object) {
// ...
}
class CustomObject {
// ...
public void customMethod() {
// ...
}
// ...
}
示例中的 CustomObject 对象是线程安全的,因为CustomObject 对象只被 method1 和 method2 两个方法使用,这两个方法的返回值都是 void,并没有将 CustomObject 对象传递给外部对象。
(3) 对象成员
对象成员存储在堆上,如果不同的线程同时修改同一个对象的同一个成员,那么代码就不是线程安全的。
(4) 线程控制逃逸规则
如果一个资源的创建,使用,销毁都在同一个线程内完成,且永远不会脱离该线程的控制,则该资源的使用就是线程安全的。
不过即使对象本身线程安全,但如果该对象中包含其他资源(文件,数据库连接),整个应用也许就不再是线程安全的了。
示例:
线程 A 和 B 都向数据库中插入主键为 K 的记录
首先线程 A 运行,检查到主键 K 不存在
切换到线程 B 运行,也检查到主键 K 不存在
切换到线程 A 插入主键为 K 的记录
切换到线程 B 也插入主键为 K 的记录
2 线程安全与不可变性
可以通过创建不可变的共享对象保证对象在线程间共享时不会被修改,从而实现线程安全
示例
class ImmutableValue {
private int value = 0;
public ImmutableValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
ImmutableValue 类的成员变量 value 是通过构造函数赋值的,并且在类中没有 set 方法。这意味着一旦 ImmutableValue 实例被创建,value 变量就不能再被修改,这就是不可变性。
注意,“不变”(Immutable)和“只读”(Read Only)是不同的。当一个变量是“只读”时,变量的值不能直接改变,但是可以在其它变量发生改变的时候发生改变。比如,一个人的出生年月日是“不变”属性,而一个人的年龄便是“只读”属性,但是不是“不变”属性。随着时间的变化,一个人的年龄会随之发生变化,而一个人的出生年月日则不会变化。这就是“不变”和“只读”的区别。(摘自《Java 与模式》第 34 章)
如果你需要对 ImmutableValue 类的实例进行操作,可以通过得到 value 变量后创建一个新的实例来实现,下面是一个对 value 变量进行加法操作的示例:
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 类实例返回,而不是直接对它自己的 value 变量进行操作。
引用不是线程安全的,即使一个对象是线程安全的不可变对象,指向这个对象的引用也可能不是线程安全的。
public void 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 类本身还是可变的,因此 Calculator 类不是线程安全的。换句话说:ImmutableValue 类是线程安全的,但使用它的类不是。当尝试通过不可变性去获得线程安全时,这点是需要牢记的。
要使 Calculator 类实现线程安全,将 getValue()、setValue()和 add()方法都声明为同步方法即可。