CopyOnWriteArrayList 复合操作需要程序自己控制线程安全
特性 1 2 public class CopyOnWriteArrayList <E> implements List <E>, RandomAccess, Cloneable, java.io.Serializable {
特性基本与arrayList一致,底层也是数组结构
基本属性 1 2 3 4 5 private static final long serialVersionUID = 8673264195747942595L ;final transient ReentrantLock lock = new ReentrantLock ();private transient volatile Object[] array;
构造器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public CopyOnWriteArrayList () { setArray(new Object [0 ]); } public CopyOnWriteArrayList (E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); } public CopyOnWriteArrayList (Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); }
添加元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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(); } }
指定位置添加元素 分段拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public void add (int index, E element) { final ReentrantLock lock = this .lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (index > len || index < 0 ) throw new IndexOutOfBoundsException ("Index: " +index+ ", Size: " +len); Object[] newElements; int numMoved = len - index; if (numMoved == 0 ) newElements = Arrays.copyOf(elements, len + 1 ); else { newElements = new Object [len + 1 ]; System.arraycopy(elements, 0 , newElements, 0 , index); System.arraycopy(elements, index, newElements, index + 1 , numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }
addIfAbsent 添加一个不存在的元素 如果存在就不添加返回false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public boolean addIfAbsent (E e) { Object[] snapshot = getArray(); 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 { Object[] current = getArray(); int len = current.length; if (snapshot != current) { 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(); } }
ReentrantLock:可重入锁,灵活,手工加锁和释放锁,可以设置超时时间及响应中断
synchronize:简单,加锁,释放锁都是自动的 ,如果获取不到锁就会排队等待
获取指定位置元素 1 2 3 4 5 6 7 8 9 10 public E get (int index) { return get(getArray(), index); } final Object[] getArray() { return array; } private E get (Object[] a, int index) { return (E) a[index]; }
这个方法是线程不安全的,因为这个分成了两步,分别是获取数组和获取元素,而且中间过程没有加锁。假设当前线程在获取数组(执行getArray())后,其他线程修改了这个CopyOnWriteArrayList,那么它里面的元素就会改变,但此时当前线程返回的仍然是旧的数组,所以返回的元素就不是最新的了,这就是写时复制策略产生的弱一致性问题 。
修改指定位置元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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; setArray(newElements); } else { setArray(elements); } return oldValue; } finally { lock.unlock(); } }
删除元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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(); } }
弱一致性的迭代器 指返回迭代器后,其他线程对list的增删改对迭代器是不可见的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public Iterator<E> iterator () { return new COWIterator <E>(getArray(), 0 ); } static final class COWIterator <E> implements ListIterator <E> { private final Object[] snapshot; private int cursor; private COWIterator (Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } public boolean hasNext () { return cursor < snapshot.length; } @SuppressWarnings("unchecked") public E next () { if (! hasNext()) throw new NoSuchElementException (); return (E) snapshot[cursor++]; }
如果在返回迭代器后没有对里面的数组array进行修改,则这两个变量指向的确实是同一个数组;但是若修改了,则根据前面所讲,它是会新建一个数组,然后将修改后的数组复制到新建的数组,而老的数组就会被“丢弃”,所以如果修改了数组,则此时snapshot指向的还是原来的数组,而array变量已经指向了新的修改后的数组了。这也就说明获取迭代器后,使用迭代器元素时,其他线程对该list的增删改不可见,因为他们操作的是两个不同的数组,这就是弱一致性。
CopyOnWriteArrayList使用写时复制 策略保证list的一致性,而获取–修改–写入三个步骤不是原子性,所以需要一个独占锁保证修改数据时只有一个线程能够进行。另外,CopyOnWriteArrayList提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对list的修改是不可见的,迭代器遍历的数组是一个快照。
使用场景及优点 并发容器用于读多写少的并发场景。比如白名单,黑名单等场景,热点数据缓存
读操作可能会远远多于写操作的场景。比如,有些系统级别的信息,往往只需要加载或者修改很少的次数,但是会被系统内所有模块频繁的访问。对于这种场景,我们最希望看到的就是读操作可以尽可能的快,而写即使慢一些也没关系。
CopyOnWriteArrayList 的思想比读写锁的思想更进一步。为了将读取的性能发挥到极致,CopyOnWriteArrayList 读取是完全不用加锁的 ,更厉害的是,写入也不会阻塞读取操作 ,也就是说你可以在写入的同时进行读取,只有写入和写入之间需要进行同步,也就是不允许多个写入同时发生,但是在写入发生时允许读取同时发生。这样一来,读操作的性能就会大幅度提升。
读写分离
读多写少,对一致性要求不高,数据量不大
复制时瞬间占用内存
写时复制:写操作的时候,把List复制一份做写操作,操作完赋值回去 加锁(写锁,不阻塞读操作)
缺点 内存占用,弱一致性