大多数情况下读操作是远远大于写操作的,读操作本身不会修改集合中的数据,所以说读操作和写操作一样加锁的话对资源是一种极大的浪费。而java.util.concurrent包刚好为我们提供了一种读不加锁写加锁的集合,而且读写不互斥,只有写写之间才会阻塞,让我们在构建多线程的应用时,既保证了运行效率,又保证了数据的一致性,废话不多说,我们直接开撸源码。
首先是属性,CopyOnWriteArrayList重要的属性比较少:
/** 保护所有并发资源的可重入锁 */
final transient ReentrantLock lock = new ReentrantLock();
/** 集合底层的对象数组,只能通过getArray/setArray访问*/
/** 使用volatile保证了线程间的可见性*/
private transient volatile Object[] array;
/** 调用native方法的UNSAFE类 */
private static final sun.misc.Unsafe UNSAFE;
/** 集合的锁属性相对于集合对象在内存中起始地址的偏移量 */
private static final long lockOffset;
读数据的get(int index)函数
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
可以看到实现非常简单,直接返回数组中参数下标对应的元素。
重点在于写操作,下面来看add(E e)函数
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
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();
}
}
CopyOnWriteArrayList ?类的所有可变操作(add,set等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,并不直接修改原有数组对象,而是对原有数据进行一次拷贝,将修改的内容写入副本中。写完之后,再将修改完的副本替换成原来的数据,这样就可以保证写操作不会影响读操作了。
从?CopyOnWriteArrayList ?的名字可以看出,CopyOnWriteArrayList ?是满足?CopyOnWrite ?的 ArrayList,所谓?CopyOnWrite ?的意思:、就是对一块内存进行修改时,不直接在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后,再将原来指向的内存指针指到新的内存,原来的内存就可以被回收。
|