一,类基本属性
public class CopyOnWriteArrayListimplements List , RandomAccess, Cloneable, java.io.Serializable { //可重入锁 final transient ReentrantLock lock = new ReentrantLock(); //底层是个volatile修饰的array private transient volatile Object[] array; final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; } public CopyOnWriteArrayList() { setArray(new Object[0]); }
可见初始化方法是将new Object[0]赋值给了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;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
可见是使用了重入锁加索,保证的线程安全。
使用Arrays.copyOf,复制原有数据到新的数据中,开销较大。
三,读数据
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
直接取对应下标中的数据,并没有加锁。
四,读没有加锁,是怎么保证可见性的呢?
private transient volatile Object[] array;
由于array被volatile修饰,所以每次读取都会从主存中读取,故以volatile关键字,保证的不加锁实现的可见性。
五,A线程在循环读取时,B线程删除数据,会导致下标越界吗?
public Iteratoriterator() { return new COWIterator (getArray(), 0); } //迭代器的实现 static final class COWIterator implements ListIterator { private final Object[] snapshot; private int cursor; //将数组赋值给快照数组 private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } //查询快照长度 public boolean hasNext() { return cursor < snapshot.length; } public boolean hasPrevious() { return cursor > 0; } public E next() { if (! hasNext()) throw new NoSuchElementException(); //返回快照中的数据 return (E) snapshot[cursor++]; }
通过分析,我们发现A线程读取的是快照即原始数据,B线程修改不会影响A线程的遍历内容和结果。故不会报错。
六.volatile只修饰了array的引用,修改数据怎么保证数组内容的可见性?
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;
//可以看到新建的array
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
是的,如果仅仅是array引用被volatile修饰的话,是无法保证其数组内容的可见性的,所以这里的实现是修改时重新copy数据,从而使array引用发现改变。保证了修改数组数据的可见性。修改一个数据要复制整个数组,代价也相当的高了。
综上,CopyonWriteArrayList 通过重入锁加在写操作上,并且对底层数组使用volatile修饰,从而实现的线程安全和读不加锁。由于增加和修改都需要复制整个数组,故适合读多写少的场景。
CopyonWriteArraySet一,构造函数
public class CopyOnWriteArraySetextends AbstractSet implements java.io.Serializable { private static final long serialVersionUID = 5457747651344034263L; private final CopyOnWriteArrayList al; //内部是一个CopyOnWriteArrayList public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList (); }
惊奇的发现,CopyOnWriteArraySet的内部竟然是个CopyOnWriteArrayList。
二,那么怎么实现的set唯一性的呢?
public boolean add(E e) {
return al.addIfAbsent(e);
}
public boolean addIfAbsent(E e) {
//注意,此时并未加锁
Object[] snapshot = getArray();
//如果快照中有,则直接返回false,这样可以减少锁的开销。
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//重新获取底层array
Object[] current = getArray();
int len = current.length;
//如果array发生了改变,需再次判断是否已存在
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
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();
}
}
靠的是遍历查询,如果没有再add。其中add的过程较CopyOnWriteArrayList复杂,主要是想避免比较是否存在时产生的锁的开销。
另外遍历查询的效率也太低了,时间复杂度是O(n),远远不如hashmap的O(1)。所以这个set在大数据量的情况下,性能堪忧。



