如何声明 CopyOnWriteArrayList 的锁?

CopyOnWriteArrayList 的源码中,lock 字段的声明包含 finaltransient 两个关键字。

final transient ReentrantLock lock = new ReentrantLock();

1. final 的作用

  • 不可变性

    • final 表示 lock 引用一旦初始化后不能再指向其他对象(即不能重新赋值),保证引用在对象生命周期内不可变。。
    • 确保锁对象的唯一性,防止多线程环境下因锁被意外替换而导致并发问题。
  • 线程安全

    • final 保证锁对象的初始化对其他线程可见(遵循 JMM 的 happens-before 规则)。
    • 避免因指令重排序导致的未初始化锁被误用。

2. transient 的作用

  • 序列化排除
    • transient 表示该字段不会被默认的序列化机制保存
    • 原因:ReentrantLock 是线程同步的内部状态工具,序列化锁对象无意义,且可能破坏线程安全性。
    • 反序列化时会重新创建一个新的锁对象(通过 readObject 方法初始化)。

3. 结合设计意图的分析

  1. final 的必要性

    • CopyOnWriteArrayList 依赖唯一的 lock 控制所有写操作的互斥性,若允许修改锁引用,会导致线程失去同步保障。
    • 示例:若锁被替换,两个线程可能分别使用不同的锁,失去互斥效果。
  2. transient 的必要性

    • 锁对象的状态(如持有锁的线程、重入次数)是运行时动态的,序列化这些状态无意义。
    • 反序列化后必须生成新锁,以保持线程安全语义的一致性。

4. 对比其他场景

  • final 的典型用途
    • 常量、不可变对象(如 String)、线程共享的引用(如本例中的锁)。
  • transient 的典型用途
    • 敏感数据(如密码)、运行时动态状态(如锁、线程池)、冗余或可重建的数据。

5. 反序列化时的处理

CopyOnWriteArrayListreadObject 方法中会重新初始化锁(伪代码):

private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    resetLock(); // 重新初始化锁
    // ...
}

private void resetLock() {
    NSAFE.putObjectVolatile(this, lockOffset, new ReentrantLock());
}

对序列化和反序列化感兴趣的可参考上篇笔记。CopyOnWriteArrayList 实现了 Serializable 接口,因此可以通过 readObject 方法来控制反序列化流程。


总结

关键字 作用 设计意图
final 确保锁引用不可变,保障线程安全 防止锁对象被意外替换,破坏同步机制。
transient 禁止锁对象序列化,反序列化时重建 锁是运行时状态,序列化无意义且不安全。

这两个关键字共同确保了 CopyOnWriteArrayList 在并发和序列化场景下的正确性。

posted @ 2025-04-06 22:53  皮皮是个不挑食的好孩子  阅读(14)  评论(0)    收藏  举报