copyonwritearraylist通过volatile引用、reentrantlock写锁和不可变快照语义三重保障实现泛型数组替换的线程安全:volatile确保array引用更新及内存写入的可见性,reentrantlock保证写操作原子性,迭代器基于快照避免类型擦除风险。

Java中泛型数组在CopyOnWriteArrayList内部array替换时的线程安全,核心靠三重保障:volatile引用、ReentrantLock写锁、不可变快照语义。它不依赖泛型擦除后的类型安全性来保证并发正确性,而是通过内存可见性与操作原子性确保多线程读写互不干扰。
volatile修饰的array引用确保可见性
CopyOnWriteArrayList内部用private transient volatile Object[] array存储数据。虽然泛型在运行时被擦除(E → Object),但volatile关键字保证了两点:
- 任何线程对array引用的更新(如setArray(newArray))都会立即对其他线程可见
- 写操作前的所有内存写入(如新数组元素赋值)对读线程也具有happens-before关系
- 避免因CPU缓存不一致导致读线程看到“半初始化”的新数组
ReentrantLock保护写操作的原子性
所有可变操作(add、remove、set)都包裹在lock.lock()/unlock()块中:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 同一时刻最多一个线程能进入写临界区,杜绝多个线程并发复制+替换造成的丢失更新
- 复制数组(Arrays.copyOf)、修改副本、调用setArray()这三步构成一个逻辑原子单元
- 即使泛型无法在运行时检查类型,lock也确保了“旧数组→新数组”切换过程不被中断或交错
迭代器基于快照,天然规避泛型数组的类型擦除风险
get()、iterator()等读操作完全不加锁,直接访问当前array引用指向的数组:
- Iterator构造时通过getArray()拿到当时array的副本引用,后续遍历始终基于该不可变快照
- 由于数组本身不被修改(只被整体替换),不存在元素类型在遍历中突变的问题
- 泛型擦除不影响读取——读到的是Object[],由调用方强制转型(如(String)list.get(0)),而这个转型发生在单线程上下文中,无并发冲突
为什么不用泛型数组直接声明?
Java禁止创建具体泛型数组(如new E[10]),所以CopyOnWriteArrayList只能用Object[]承载元素。但这不是缺陷,反而是设计关键:
- 所有写操作先完成Object[]副本的填充,再通过volatile引用发布,类型安全由编译期泛型约束+运行时转型共同保障
- 如果强行用E[]声明,需用unchecked cast(如(E[]) new Object[n]),反而引入更多类型风险;当前方案把类型检查推迟到get()调用点,更可控
- volatile + lock 组合已覆盖最危险的并发场景:读线程不会看到“正在构造中”的数组,也不会看到部分更新的中间状态
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










