CopyOnWriteArrayList&CopyOnWriteArraySet_写入时复制
在非并发中,一般常用的集合有ArrayList,HashSet,HashMap。并发中有ConcurrentHashMap,CopyOnWriteArrayList,CopyOnWriteArraySet来保证线程的安全。今天就学习下CopyOnWriteArrayList和CopyOnWriteArraySet。
CopyOnWrite,顾名思义在Write的时候才会Copy,Read的时候是不会Copy的,这样我们就理解了什么叫写入时复制,即写的时候,我们对写操作加一个锁,用来保证数据的安全性,读操作是不会修改数据的,所以不需要加锁。
CopyOnWriteArrayList:
这是线程安全的有序集合,在CopyOnWriteArrayList中,定义了Lock用来实现锁,一个volatile Object数组用来存储数据。
final transient ReentrantLock lock = new ReentrantLock(); private transient volatile Object[] array;
再看下常用的add,remove方法,可以看到在对添加或者删除之前都是加了Lock锁的,在操作完成后再释放锁:
/**********/
final void setArray(Object[] a) {
array = a;
}
/*****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(); //释放锁
}
}
/*****remove*****/
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
而在get方法中,却是简单粗暴,直接通过数组下标拿到了值:
public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }
这样我们理解CopyOnWriteArrayList还是挺简单的,在读的时候不加锁,添加和删除加锁。很适合读多写少的数据类型,并发效率更高。但也有些问题,比如我们在读数据时,其他线程在更改旧数据,这样会读取旧数据。添加和删除会频繁的复制数组,可能浪费内存引起GC。
CopyOnWriteArraySet:
HashSet和CopyOnWriteArraySet都有同一个父亲(AbstractSet),它们又喜欢了不同的人(HashMap&CopyOnWriteArrayList)。在HashSet中,底层是定义了一个HashMap,利用HashMap的键不可重复性实现了Set数据的不可重复性。而在CopyOnWriteArraySet中,底层是基于CopyOnWriteArrayList实现的。我们了解了CopyOnWriteArrayList后,理解CopyOnWriteArraySet就简单很多了,因为它的方法基本都是调用CopyOnWriteArrayList的方法。
先看一下它的无参构造方法,直接new了一个CopyOnWriteArrayList:
private final CopyOnWriteArrayList<E> al; public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); }
再看看Set里常用的方法,add、remove、contains、clear。发现全是调用CopyOnWriteArrayList的方法,改都不带改的,真的是爱的深沉啊。
public boolean add(E e) { return al.addIfAbsent(e); } public boolean remove(Object o) { return al.remove(o); } public boolean contains(Object o) { return al.contains(o); } public void clear() { al.clear(); }
看到这里,我们还是没看到CopyOnWriteArraySet是如何实现不可重复性的,继续点进add里,在addIfAbsent里会根据indexOf的返回值判读e元素是已经有了,还是执行add操作。而在indexOf里,我们可以看到是for循环遍历找下标,我和我的小伙伴都惊呆了!居然是for循环!无语>"P{$%*&^*#$^^>:"%^&$!@#
这体现出了CopyOnWriteArraySet的弊端,适合数据量小的集合,大数据量下的for循环、复制数组谁遭的住啊!
public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); //这里根据indexOf的返回值判断是否存在,若大于等于0则存在 return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } //indexOf:循环遍历得到e的下标,不存在则返回-1 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; } /* * A version of addIfAbsent using the strong hint that given * recent snapshot does not contain e. *最近的不包含e的addIfAbsent快照 */ 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) { // 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(); } }
看到这里,基本就了解CopyOnWriteArraySet,毕竟除了不可重复性,和CopyOnWriteArrayList几乎一模一样的。虽然没有代码,我们还是可以想到CopyOnWriteArraySet的remove怎么实现,第一步,先用indexOf找要删除的元素e的下标,如果没找到就返回false,有则执行第二步。第二步,上锁,复制数组,删除元素e,再把新数组复制回来覆盖,释放锁。

浙公网安备 33010602011771号