1. 原理
CopyOnWriteArrayList有点像线程平安的ArrayList.
其实它的原理简略概括起来就是读写拆散.写操作是在一个复制的数组上进行的,读操作在原始数组中进行,读写是拆散的.写操作的时候是加锁了的,写操作实现了之后将原来的数组指向新的数组.
上面咱们简略看下add和get办法是如何实现写读操作的.
/** * 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(); }}@SuppressWarnings("unchecked")private E get(Object[] a, int index) { return (E) a[index];}/** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */public E get(int index) { return get(getArray(), index);}
2. 实用场景
因为每次写数据的时候都会开拓一个新的数组,这样就会消耗内存,而且加锁了,写的性能不是很好.而读操作是十分迅速的,并且还反对在写的同时能够读.
所以就非常适合读多写少的场景.
3. 毛病
- 内存耗费大: 每次写操作都须要复制一个新的数组,所以内存占用是十分大的
- 数据不统一: 读数据的时候可能读取到的不是最新的数据,因为可能局部写入的数据还未同步到读的数组中.
对内存敏感和实时性要求很高的场景都不适宜.
4. CopyOnWriteArraySet
在翻阅CopyOnWriteArrayList源码过程中,偶然间发现CopyOnWriteArraySet的外部竟然就是用一个CopyOnWriteArrayList实现的.
public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable { private final CopyOnWriteArrayList<E> al; /** * Adds the specified element to this set if it is not already present. * More formally, adds the specified element {@code e} to this set if * the set contains no element {@code e2} such that * <tt>(e==null ? e2==null : e.equals(e2))</tt>. * If this set already contains the element, the call leaves the set * unchanged and returns {@code false}. * * @param e element to be added to this set * @return {@code true} if this set did not already contain the specified * element */ public boolean add(E e) { return al.addIfAbsent(e); }}
而CopyOnWriteArrayList的addIfAbsent办法其实和add办法外部实现是差不多的(都是新复制数组且上锁),只不过多了层判断
/** * Appends the element, if not present. * * @param e element to be added to this list, if absent * @return {@code true} if the element was added */public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot);}/** * A version of addIfAbsent using the strong hint that given * recent snapshot does not contain e. */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(); }}
5. 扩大 : CopyOnWriteHashMap
Java没有提供相似CopyOnWriteHashMap的类,可能是曾经有ConcurrentHashMap了吧.明确了CopyOnWriteArrayList的思维,咱们其实还能够模拟着写一个简略的CopyOnWriteHashMap
import java.util.Collection;import java.util.Map;import java.util.Set; public class CopyOnWriteHashMap<K, V> implements Map<K, V>, Cloneable { private volatile Map<K, V> internalMap; public CopyOnWriteHashMap() { internalMap = new HashMap<K, V>(); } public V put(K key, V value) { synchronized (this) { Map<K, V> newMap = new HashMap<K, V>(internalMap); V val = newMap.put(key, value); internalMap = newMap; return val; } } public V get(Object key) { return internalMap.get(key); }}
6. CopyOnWriteArrayList为啥比Vector性能好?
在Vector外部,增删改查都进行了synchronized润饰,每个办法都要去锁,性能会大大降低.而CopyOnWriteArrayList只是把增删改加锁了,所以CopyOnWriteArrayList在读方面显著好于Vector.所以CopyOnWriteArrayList最好是在读多写少的场景下应用.