Java 中的写时复制 (Copy on Write, COW)

Background

写时复制 (Copy on Write, COW) 有时也叫 "隐式共享", 顾名思义, 就是让全部须要使用资源 R 的使用者共享资源 R 的同一个副本, 当其中的某一个使用者要对资源 R 进行修改操做时, 先复制 R 的一个副本 R' , 再进行修改操做;java

Problem

在 Java 集合框架中, 像 ArrayList, HashSet 等基础集合类是非线程安全的, 在多线程环境中同时进行遍历和修改操做可能会出现 ConcurrentModificationException; 能够对每一个操做都进行同步以解决这个问题, 但对于大部分操做是读取数据的集合进行同步可能会使性能急剧降低, 在这种状况下这种性能损失是没有必要的;数组

举个例子, 如下的坐标中, x 轴表示时间轴, y 轴表示不一样的线程, + 表示读取操做, * 表示修改操做:安全

| ++ ++ ++   ++ ++ ++ ++
|++ + ++ +  ++ + +++ + +
|  ++ ++ *    ++ +* ++ +
| ++ + +   * ++ +  ++ + 
|+ + +++ *  + + +++ + ++
| + + +      + + + + + +
+----------------------
         1 2      3
复制代码

其中除了 1 2 3 这三个时刻以外, 其余时间都是只有读取操做, 在除了 1 2 3 以外的时间进行同步就是没有必要的;多线程

所以咱们但愿使用一种技术来处理那些在多线程环境下 "读取操做" 远远多于 "修改操做" 的资源, 使得不须要经过对每个操做都进行同步;框架

Solution

Java 类库中提供了两个 Copy-on-Write 的类: CopyOnWriteArrayListCopyOnWriteArraySet, 分别实现了 ListSet 两个接口;性能

How CopyOnWriteArrayList Works

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)"; 所以 CopyOnWriteArrayListCopyOnWriteArraySet 只能适用于对数据实时性要求不高的场景;

How CopyOnWriteArraySet Works

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);
}
复制代码

是很是典型的 组合模式 应用;

相关文章
相关标签/搜索