写时复制 (Copy on Write, COW) 有时也叫 "隐式共享", 顾名思义, 就是让全部须要使用资源 R 的使用者共享资源 R 的同一个副本, 当其中的某一个使用者要对资源 R 进行修改操做时, 先复制 R 的一个副本 R' , 再进行修改操做;java
在 Java 集合框架中, 像 ArrayList
, HashSet
等基础集合类是非线程安全的, 在多线程环境中同时进行遍历和修改操做可能会出现 ConcurrentModificationException
; 能够对每一个操做都进行同步以解决这个问题, 但对于大部分操做是读取数据的集合进行同步可能会使性能急剧降低, 在这种状况下这种性能损失是没有必要的;数组
举个例子, 如下的坐标中, x 轴表示时间轴, y 轴表示不一样的线程, +
表示读取操做, *
表示修改操做:安全
| ++ ++ ++ ++ ++ ++ ++
|++ + ++ + ++ + +++ + +
| ++ ++ * ++ +* ++ +
| ++ + + * ++ + ++ +
|+ + +++ * + + +++ + ++
| + + + + + + + + +
+----------------------
1 2 3
复制代码
其中除了 1 2 3 这三个时刻以外, 其余时间都是只有读取操做, 在除了 1 2 3 以外的时间进行同步就是没有必要的;多线程
所以咱们但愿使用一种技术来处理那些在多线程环境下 "读取操做" 远远多于 "修改操做" 的资源, 使得不须要经过对每个操做都进行同步;框架
Java 类库中提供了两个 Copy-on-Write 的类: CopyOnWriteArrayList
和 CopyOnWriteArraySet
, 分别实现了 List
和 Set
两个接口;性能
CopyOnWriteArrayList
只有在对其进行修改操做时才会进行同步操做, 所以其 add
, remove
等方法中均使用了同步机制; 在 CopyOnWriteArrayList
中, 定义了一个可重入锁:this
final transient ReentrantLock lock = new ReentrantLock();
复制代码
该锁用于对全部修改集合的方法 (add
, remove
等) 进行同步, 在进行实际修改操做时, 会先复制原来的数组, 再进行修改, 最后替换原来的:spa
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
复制代码
因为在修改时复制了一份数据, 所以全部读取操做都无需进行同步:线程
public E get(int index) {
return get(getArray(), index);
}
复制代码
但也会所以引入 "弱一致性" 问题; 所谓 "弱一致性" 是指当一个线程正在读取数据时, 若此时有另外一个线程同时在修改该区域的数据, 读取的线程将没法读取最新的数据, 即该读取线程只能读取到它读取时刻之前的最新数据;code
"弱一致性" 的另外一个体现是当使用迭代器的时候, 使用迭代器遍历集合时, 该迭代器只能遍历到建立该迭代器时的数据, 对于建立了迭代器后对集合进行的修改, 该迭代器没法感知; 这是由于建立迭代器时, 迭代器对原始数据建立了一份 "快照 (Snapshot)"; 所以 CopyOnWriteArrayList
和 CopyOnWriteArraySet
只能适用于对数据实时性要求不高的场景;
CopyOnWriteArraySet
的实现是基于 CopyOnWriteArrayList
的, 其内部维护了一个 CopyOnWriteArrayList
实例 al
:
private final CopyOnWriteArrayList<E> al;
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
复制代码
全部对 CopyOnWriteArraySet
的操做都被委托给 al
, 如 add
方法:
public boolean add(E e) {
return al.addIfAbsent(e);
}
复制代码
是很是典型的 组合模式 应用;