Copy-On-Write 简称 COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始你们都在共享同一个内容,当某我的想要修改这个内容的时候,才会真正把内容 Copy 出去造成一个新的内容而后再改,这是一种延时懒惰策略。从 JDK1.5 开始 Java 并发包里提供了两个使用 CopyOnWrite 机制实现的并发容器:java
CopyOnWriteArrayList
ArrayList 线程安全的实现CopyOnWriteArraySet
Set 线程安全的实现CopyOnWrite 容器即写时复制的容器。通俗的理解是当咱们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,而后新的容器里添加元素,添加完元素以后,再将原容器的引用指向新的容器。这样作的好处是咱们能够对 CopyOnWrite 容器进行并发的读,而不须要加锁,由于当前容器不会添加任何元素。因此 CopyOnWrite 容器也是一种读写分离的思想,读和写不一样的容器。数组
CopyOnWrite 并发容器用于读多写少的并发场景。使用 CopyOnWriteMap 须要注意两件事情:安全
减小扩容开销。根据实际须要,初始化 CopyOnWriteMap 的大小,避免写时 CopyOnWriteMap 扩容的开销。多线程
使用批量添加。由于每次添加,容器每次都会进行复制,因此减小添加次数,能够减小容器的复制次数。并发
CopyOnWrite 容器有不少优势,可是同时也存在两个问题,即内存占用问题和数据一致性问题。因此在开发的时候须要注意一下。源码分析
针对内存占用问题,能够经过压缩容器中的元素的方法来减小大对象的内存消耗,好比,若是元素全是 10 进制的数字,能够考虑把它压缩成 36 进制或 64 进制。或者不使用 CopyOnWrite 容器,而使用其余的并发容器,如 ConcurrentHashMap。优化
List<String> list = new CopyOnWriteArrayList<>(); list.add("a"); list.add("b"); list.get(0);
在使用 CopyOnWriteArrayList 以前,咱们先阅读其源码了解下它是如何实现的。如下代码是向 ArrayList 里添加元素,能够发如今添加的时候是须要加锁的,不然多线程写的时候会 Copy 出 N 个副本出来。this
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 添加数据,读仍是会读到旧的数据,由于写的时候不会锁住旧的 ArrayList。线程
public E get(int index) { return get(getArray(), index); }
实现很简单,只要了解了 CopyOnWrite 机制,咱们能够实现各类 CopyOnWrite 容器,而且在不一样的应用场景中使用。设计
CopyOnWriteArraySet 底层所有使用 CopyOnWriteArrayList 实现。
public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } // 增删改查都是调用 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); }
以 addIfAbsent 为例
public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); // indexOf 查找指定元素e在snapshot数组中的索引位置 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) { // 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(); } }
参考:
天天用心记录一点点。内容也许不重要,但习惯很重要!