生活
所有的程序员都剧作家,而所有计算机都是糟糕的演员。
CopyOnWriteArrayList介绍
还记得学集合的时候,学的第一个集合就是ArrayList.它是一个由数组实现的集合。因为他对数组的增删改和查询都是不加锁的,所以它并不是线程安全的。
因此,我们会引入到一个线程安全的集合,Vector,他的底层也是数组,与ArrayList不同的是,Vector里对元素的增删改查都是加了synchronized,因此说他是线程安全的。但是这种在任何场景都加锁的集合显然效率不高。
因此在多线程高并发环境下,引入了今天要研究的CopyOnWriteArrayList,他是一个写时复制的一个集合,具体来说,它的底层还是一个数组,在查询的时候直接查这个数组里的元素,不需要加锁,但是在做增删改操作时,会获取到互斥锁,并且copy原数组的数据到新数组【在新数组内进行增删改操作】,执行完这些操作后,把集合数组引用指向新数组,因此在增删改下不会对查询产生影响,但是因为在做增删改操作时每次都需要复制新数组,非常耗费性能,因此这个集合适用在多读少写的场景,比如缓存。
注意:如果你希望能马上读到刚写进去的数据,就不适用这个集合了。
成员
//互斥锁
transient final ReentrantLock lock = new ReentrantLock();
//容器数组
private volatile transient Object[] array;
CopyOnWriteArrayList方法源码解析
简单的看了一下CopyOnWriteArrayList的源码,非常简单而且通俗易懂,这里就贴一下增加数据和查询数据的操作,主要理解一个解锁,一个不加锁,以及写时复制的实现。
添加元素:
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();
}
}
查询
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
//可以看到确实不加锁
public E get(int index) {
return get(getArray(), index);
}
CopyOnWriteArraySet
CopyOnWriteArraySet 是HashSet的并发实现。底层就是使用CopyOnWriteArrayList,要知道Set是不包含重复元素的,因此可以看到它底层实际添加元素的方法是调用了CopyOnWriteArrayList
addIfAbsent
public boolean addIfAbsent(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// Copy while checking if already present.
// This wins in the most common case where it is not present
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = new Object[len + 1];
for (int i = 0; i < len; ++i) {
//如果有相同元素,直接放弃添加
if (eq(e, elements[i]))
return false; // exit, throwing away copy
else
newElements[i] = elements[i];
}
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}