CopyOnWriteArrayList介绍
官网介绍CopyOnWriteArrayList是一个ArrayList的线程安全的版本的实现,但是它保证线程安全的原理是add和set这系列写操作都是通过复制一个数组的副本来实现的,但是这也带来了高昂的性能消耗。
接口继承图以及相关方法分析
接口继承图和ArrayList一模一样,之前我们已经分析过了,可以点击这里回顾。
数据结构以及构造方法分析
//重入锁,保障线程安全
final transient ReentrantLock lock = new ReentrantLock();
/** 存放数据的数组,只能通过getArray和setArray来访问 */
private transient volatile Object[] array;
/**
* 获取存放数据的数组
*/
final Object[] getArray() {
return array;
}
/**
* 设置存放数据的数组
*/
final void setArray(Object[] a) {
array = a;
}
/**
* 构造方法:创建一个空的数组
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* 创建一个包含指定集合的CopyOnWriteArrayList
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
//如果是CopyOnWriteArrayList,直接获取对应的array
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
//否则调用集合的方法进行转换
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652) jdk bug 6260652
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
/**
* 创建包含「给定数组的深复制版本」的CopyOnWriteArrayList
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
分析:这里有两个小要点,第一,为了保证array在多线程下的可见性,用了volatile进行修饰。 第二,为了防止array被未知路径篡改,array这个变量被设置成private,同时getArray和setArray方法被设置成final,不允许重写以及method scope被设置成default,这样就只能就很好的保护了array变量。
重要方法分析
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;
//替换CopyOnWriteArrayList数据结构中原来的数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
set方法
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
//深复制出一个新的数组副本出来
Object[] newElements = Arrays.copyOf(elements, len);
//把新元素放到数组里面
newElements[index] = element;
//替换CopyOnWriteArrayList数据结构中原来的数组
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
其实看到这里,基本也就明白了CopyOnWriteArrayList的精髓,使用volatile保障array在多线程访问下的可见性。使用重入锁保证array的写入安全,最后咱们再看几个读方法的实现就可以了。
public int size() {
return getArray().length;
}
public int indexOf(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length);
}
通过上面的实现,咱们可以看到,读方法都是先通过getArray()获取当前array的副本,然后进行详情的数组操作就可以了。
CopyOnWriteArraySet介绍
CopyOnWriteArraySet你就可以理解为一个线程安全的set,但是它是用CopyOnWriteArrayList实现的,所以有CopyOnWriteArrayList一样的问题。
数据结构以及构造方法分析
//数据结构和CopyOnWriteArrayList一致
private final CopyOnWriteArrayList<E> al;
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
重要方法分析
add方法
//使用addIfAbsent方法完美的实现了add的效果
public boolean add(E e) {
return al.addIfAbsent(e);
}
读方法
//直接取CopyOnWriteArrayList获取对应的size就行了
public int size() {
return al.size();
}