CopyOnWrite
介绍
在juc(java.util.concurrent)包下有着这么两个类,CopyOnWriteArrayList 和 CopyOnWriteArraySet。直译过来就是在写操作的时候复制
。这体现了读写分离的思想。
- 在写操作的线程,会将数组复制出来一份进行操作。而原本的数组不会做改变。
- 读线程则不会受到影响,但是可能读到的是一个过期的数据。
只能保证最终的一致性,不能保证实时的一致性。
CopyOnWriteArrayList
下面看下源码(添加的时候):
public boolean add(E e) {
// 添加的时候,上锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 原本的数组
Object[] elements = getArray();
// 原本数组的长度
int len = elements.length;
// 调用native方法进行复制
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 新的元素
newElements[len] = e;
// 替换数组
setArray(newElements);
// 成功
return true;
} finally {
// 解锁
lock.unlock();
}
}
CopyOnWriteArraySet
// 内部维护的是一个 CopyOnWriteArrayList
private final CopyOnWriteArrayList<E> al;
public boolean add(E e) {
// 会判断去重
return al.addIfAbsent(e);
}
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
// 如果遇到相同的会返回大于等于0的下标
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
// -1 则add
addIfAbsent(e, snapshot);
}
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
// 判断是否已经被设置过
if (snapshot != current) {
// 优化丢失竞争,怕与另一个add操作冲突,common取最小值
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
// 如果有一项不同,就放弃修改
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
场景:
在读多,写少的情况下适用。
缺点:
- 无法保证实时一致性
- 每次添加都会进行复制,对性能的消耗有点大