面试题答案
一键面试不可变类的线程安全性分析
- 线程安全性:在Java内存模型(JMM)中,不可变类是线程安全的。因为一旦不可变类的对象被创建,其状态就不能被修改。多个线程对不可变对象的访问,本质上是对只读数据的访问,不存在数据竞争问题。
- 字段访问与同步:不可变类的字段通常使用
final
关键字修饰。根据JMM,final
字段具有特殊的语义。对于final
字段,在对象构造完成后,就可以保证其他线程看到的是正确的值,不需要额外的同步操作。当一个线程创建不可变对象并将其引用传递给其他线程时,其他线程能看到final
字段在构造函数中被正确初始化后的值。例如:
public final class ImmutableClass {
private final int value;
public ImmutableClass(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
这里value
字段是final
的,在构造函数完成后,其他线程访问getValue()
方法总能得到正确的初始化值。
包含可变对象时的线程安全性
- 特殊规则:当不可变类对象包含其他可变对象(如数组或集合)时,要保证整体的线程安全性,需要遵循以下规则:
- 对象内部状态的封装:不可变类应将可变对象的引用声明为
private
和final
,防止外部直接修改。例如:
- 对象内部状态的封装:不可变类应将可变对象的引用声明为
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class ImmutableWithMutableInner {
private final List<String> list;
public ImmutableWithMutableInner(List<String> initialList) {
this.list = new ArrayList<>(initialList);
}
public List<String> getList() {
return Collections.unmodifiableList(list);
}
}
- **避免返回可变对象的引用**:不可变类不应直接返回可变对象的引用,而是返回其不可变视图。如上述代码中,通过`Collections.unmodifiableList()`方法返回一个不可修改的列表视图,这样外部线程无法修改内部列表。
2. 最佳实践:
- 防御性拷贝:在构造函数中,对传入的可变对象进行防御性拷贝,确保内部状态不受外部影响。如上述代码中,在构造函数中创建了一个新的ArrayList
,而不是直接使用传入的列表。
- 确保可变对象的不可变性:如果可能,在不可变类内部对可变对象进行操作时,尽量将其转换为不可变形式,如使用Collections.unmodifiableCollection
等方法。这样在不可变类内部和外部都能保证线程安全性。