Java 并发集合CopyOnWriteArrayList
1、Java在JDK1.5之前基本上对所有集合都实现了线程同步版本synchronized*,用集合工具类Collections即可得到,如下都为Collections中的方法:
static <T> Collection<T> synchronizedCollection(Collection<T> c) 返回指定 collection 支持的同步(线程安全的)collection。 static <T> List<T> synchronizedList(List<T> list) 返回指定列表支持的同步(线程安全的)列表。 static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(线程安全的)映射。 static <T> Set<T> synchronizedSet(Set<T> s) 返回指定 set 支持的同步(线程安全的)set。 static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m) 返回指定有序映射支持的同步(线程安全的)有序映射。 static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s) 返回指定有序 set 支持的同步(线程安全的)有序 set。
其内部实现是在内部维护一个对应的集合类型,然后相应所有的操作都加上同步关键字synchronized,同步方法内再调用内部集合的方法,如下为synchronizedMap的实现:
private static class SynchronizedMap<K,V>implements Map<K,V>, Serializable { private final Map<K,V> m; // Backing Map final Object mutex;SynchronizedMap(Map<K,V> m) { if (m==null) throw new NullPointerException(); this.m = m; mutex = this; }public V get(Object key) { synchronized(mutex) {return m.get(key);} }public V put(K key, V value) { synchronized(mutex) {return m.put(key, value);} } ......}?
?2、传统方式下的Collection在迭代集合时,不允许对集合进行修改,如下代码会抛出ConcurrentModificationException异常:
Collection<String> users = new ArrayList<String>();users.add("first");users.add("second");users.add("third");Iterator<String> itrUsers = users.iterator();while (itrUsers.hasNext()) {String user = itrUsers.next();if ("first".equals(user)) {users.remove(user);}System.out.println(user);}?在调用next()方法时,由于删除了元素导致期望长度和实现长度不一至而抛出异常:
public E next() { checkForComodification(); try {E next = get(cursor);lastRet = cursor++;return next; } catch (IndexOutOfBoundsException e) {checkForComodification();throw new NoSuchElementException(); }}final void checkForComodification() { if (modCount != expectedModCount)throw new ConcurrentModificationException();} }?把上面ArrayList换成CopyOnWriteArrayList即可完成在迭代中对集合进行操作,并且在多线程中也是安全的:
Collection<String> users = new CopyOnWriteArrayList<String>();
?
?3、CopyOnWriteArrayList是在Java JDK1.5中引入的并发类,在java.util.concurrent包下,它是ArrayList 的一个线程安全的变体,并且是在读时无锁的ArrayList:
public E get(int index) { return (E)(getArray()[index]); }?此方法很简单没有加锁,有可能会出现脏读的情况,但是性能非常高,对于写少读多且对脏数据要求不严的场景可以使用。
构造方法:
public CopyOnWriteArrayList() { setArray(new Object[0]); }?与ArrayList不同,CopyOnWriteArrayList创建一个大小为0的数组。
add(E)方法:add方法并没有使用内置锁,而是使用JDK1.5提供的显示锁ReentrantLock来保证线程的安全的:
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();} }?此处和ArrayList不同的是每次都创建一个新的Object数组,此数据的大小为当前数据大小加1,将之前的数组中的内容复制到新的数组中,并将新增加的对象放入到数组的末尾,最后把新数组的引用赋值给全局的数组对象:
final void setArray(Object[] a) { array = a; }?
?remove(Object)方法:和add方法一样,删除元素方法也采用JDK1.5的加锁方式来保证线程安全,但它和ArrayList采用的删除方法不同,它是创建一个长度比当前数组小1的新数组,然后遍历老数组,其它元素全部加到新数组,最后把新数组的引用赋值给全局的数组对象,它并没有使用System的arrayCopy来实现,可能会导致性能一定的下降。
?
iterator()迭代:调用iterator方法后创建一个新的COWIterator实例,并保存了一个当前数组的快照,在调用next遍历时仅对快照进行遍历,所以在迭代CopyOnWriteArrayList中操作其中元素不会像ArrayList一样抛出ConcurrentModificationException异常:
public Iterator<E> iterator() { return new COWIterator<E>(getArray(), 0); }?
?4、和ArrayList性能比较
单线程:在元素较少的情况下,两个类的性能基本上一至,但是到元素很多时,CopyOnWriteArrayList增加元素的删除元素性能会差一点
多线程:随着元素数量和线程数量的增加,CopyOnWriteArrayList在增加和删除元素的性能就会下降,而且比ArrayList性能低。但在查找元素时随着元素数量和线程数量的增加性能比ArrayList好。
在读多写少的并发场景中,CopyOnWriteArrayList比ArrayList是更好的选择。
?
?
?