1. CopyOnWrite
什么意思
- 写时复制,或称写时拷贝,是解决并发问题的一种重要思路
2. CopyOnWriteArrayList
的特点是
CopyOnWriteArrayList
实现了List
接口,用法与其他List
(如ArrayList
)基本一样,特点如下CopyOnWriteArrayList
是线程安全的,可以被多个线程并发访问CopyOnWriteArrayList
的迭代器不支持修改操作,但也不会抛出ConcurrentModificationException
异常CopyOnWriteArrayList
以原子方式支持一些复合操作
3. 基于 synchronized
的同步容器与 CopyOnWriteArrayList
的对比
- 迭代时,基于
synchronized
的同步容器需要对整个列表对象加锁,否则会抛出ConcurrentModification
异常;CopyOnWriteArrayList
没有这个问题,迭代时不需要加锁 - 复合操作时,比如先检查再更新,基于
synchronized
的同步容器需要调用方加锁;而CopyOnWriteArrayList
直接支持两个原子方法:public boolean addIfAbsent(E e)/public int addAllAbsent(Collection<? extends E> c)
- 在
CopyOnWriteArrayList
中,读不需要锁,可以并行;读和写也可以并行;但多个线程不能同时写,每个写操作都需要先获取锁 CopyOnWriteArrayList
不适用于数组很大且修改频繁的场景。它是以优化读操作为目标的,读不需要同步,性能很高,但在优化读的同时牺牲了写的性能
4. 写时复制到底是什么意思
CopyOnWriteArrayList
的内部也是一个数组,但这个数组是以原子方式被整体更新的。每次修改操作,都会新建一个数组,复制原数组的内容到新数组,在新数组上进行需要的修改,然后以原子方式设置内部的数组引用,这就是写时复制- 所有的读操作,都是先拿到当前引用的数组,然后直接访问该数组。在读的过程中,可能内部的数组引用已经被修改了,但不会影响读操作,它依旧访问原数组内容
- 换句话说:数组内容是只读的,写操作都是通过新建数组,然后原子性地修改数组引用来实现的
- 写时复制是一种重要的思维,用于各种计算机程序中,比如操作系统内部的进程管理和内存管理
- 在进程管理中,子进程经常共享父进程的资源,只有在写时才复制
- 在内存管理中,当多个程序同时访问同一个文件时,操作系统在内存中可能只会加载一份,只有程序要写时才会复制,分配自己的内存,复制可能也不会全部复制,只会复制写的位置所在的页(页是操作系统管理内存的一个单位,具体大小与系统有关,典型大小为 4 KB)
5. CopyOnWriteArrayList
的内部组成大概是
- 内部有一个数组,声明为:
private volatile transient Object[] array;
。注意有volatile
修饰,这是必需的,以保证内存可见性,即保证在写操作更改之后读操作能看到 - 内部使用
ReentrantLock
,成员声明为:transient final ReentrantLock lock = new 8 ReentrantLock();
- 默认构造方法是:
public CopyOnWriteArrayList() {setArray(new Object[0];)}
,就是设置了一个空数组
6. 保证线程安全的思路有
- 一种是锁,使用
synchronized
或ReentrantLock
- 一种是循环 CAS
- 一种是写时复制
- 锁和循环 CAS 都是控制对同一个资源的访问冲突,而写时复制通过复制资源减少冲突
- 对于绝大部分访问都是读,且有大量并发线程要求读,只有个别线程进行写,且只是偶尔写的场景,写时复制就是一种很好的解决方案
7. CopyOnWriteArraySet
的原理
CopyOnWriteArraySet
实现了Set
接口,不包含重复元素,使用比较简单CopyOnWriteArraySet
内部是通过CopyOnWriteArrayList
实现的- 与
Set
的实现类如HashSet/TreeSet
相比,CopyOnWriteArraySet
的性能比较低,不适用与元素个数特别多的集合 - 如果元素个数比较多,可以考虑
ConcurrentHashMap
或ConcurrentSkipListSet
CopyOnWriteArrayList
和CopyOnWriteArraySet
适用于读远多于写、集合不太大的场景,它们采用了写时复制,这是计算机程序中一种重要的思维和技术