将一个类改为不可变,以用于共享保证线程安全
方案1中的静态内部类默认是“可变的”,而不是“不可变的”。这是一个非常重要的区分,直接关系到线程安全的最终效果。
🔍 为什么说它是“可变”的?
在我之前给出的方案1示例中,因为类提供了setter方法,并且字段不是final的,所以它的实例在创建后,状态可以被改变:
public static class StatusAndSeq {
private String status; // 非final字段
private Integer seq; // 非final字段
// 有setter方法,意味着状态可变
public void setStatus(String status) { this.status = status; }
public void setSeq(Integer seq) { this.seq = seq; }
}
这种“可变性”正是并发风险的来源。ConcurrentHashMap只能保证你安全地存取这个对象的引用,但如果两个线程同时拿到同一个引用,并各自调用它的setter方法,就会发生数据竞争和不一致。
✅ 如何将其改造为真正的“不可变类”?
要让一个类不可变,需要遵循以下原则,我们把它应用到StatusAndSeq上:
public static class ImmutableStatusAndSeq {
// 1. 所有字段用 private final 声明
private final String status;
private final Integer seq;
// 2. 不提供setter方法
// 3. 通过构造器一次性初始化所有字段
public ImmutableStatusAndSeq(String status, Integer seq) {
this.status = status;
this.seq = seq;
}
// 4. 只提供getter方法
public String getStatus() { return status; }
public Integer getSeq() { return seq; }
// 5. (如果字段是引用类型,确保其不可变或做防御性拷贝,这里String和Integer本身就是不可变的)
}
关键区别对比表:
| 特性 | 可变类 (方案1原版) | 不可变类 (改造后) |
|---|---|---|
| 字段修饰 | private String status |
private final String status |
| setter方法 | 有 (setStatus) |
无 |
| 状态可变性 | 创建后可修改 | 创建后不可修改 |
| 并发安全性 | 低,需额外同步 | 高,可被多线程安全共享 |
| 使用方式 | 修改对象属性 | 替换整个对象 |
🛡️ 如何在并发场景中使用?
对于不可变对象,你的操作模式是“替换”而非“修改”,这天然适合ConcurrentHashMap:
@Service
public class MyDataService {
// 存储不可变对象
private final ConcurrentHashMap<String, ImmutableStatusAndSeq> dataMap = new ConcurrentHashMap<>();
// 更新数据:直接放入一个新的不可变对象
public void updateData(String key, String newStatus, Integer newSeq) {
dataMap.put(key, new ImmutableStatusAndSeq(newStatus, newSeq));
}
// 线程安全地“修改”seq:实际上是用新对象替换旧对象
public void incrementSeqAtomically(String key) {
dataMap.compute(key, (k, current) -> {
if (current == null) {
return new ImmutableStatusAndSeq("INIT", 1);
}
// 基于旧值创建全新的不可变对象
return new ImmutableStatusAndSeq(current.getStatus(), current.getSeq() + 1);
});
}
}
这种模式是最高效、最安全的。多个线程可以同时读取同一个ImmutableStatusAndSeq实例而无需任何锁。当需要“更新”时,就通过put或compute方法原子地替换为一个新的实例。
📝 与方案2(Java Record)的对比
如果你使用 Java 14+ 的 Record,那它就是天生的、语法级的不可变类:
// 这短短一行就等价于上面整个不可变类的定义
public record StatusAndSeqRecord(String status, Integer seq) {}
Record的所有字段都是private final的,没有setter,只有访问器(如status())。它清晰地向所有阅读代码的人声明:“我是不可变的数据载体”。
💡 最终建议
- 如果你的业务逻辑允许(即数据更新不极端频繁,且对象很小),强烈建议使用不可变对象。这是实现高并发读写的“银弹”。
- 选择哪种不可变实现?
- Java < 14:使用上面手动编写的不可变静态内部类。
- Java ≥ 14:毫不犹豫地使用 Record,代码最简洁,意图最明确。
- 如果业务上必须频繁修改大对象,使用不可变对象可能带来GC压力。这时,你需要退而求其次,使用可变对象 +
ConcurrentHashMap的原子方法(如compute),将所有针对一个键的“读-改-写”逻辑封装在一个原子操作内。
简单来说,方案1的静态内部类默认可变,但你可以通过将其字段设为final并移除setter,轻松将其转变为线程安全的不可变类。 这在并发设计中是一个至关重要的实践。

浙公网安备 33010602011771号