CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
之前一直没用过CopyOnWriteArrayList,只知道给ArrayList,直到多线程编程才发现ArrayList是线程非安全的,后来就了解到了CopyOnWriteArrayList;
使用ArrayList出现的问题
ArrayList<String> list=new ArrayList<String>(); // CopyOnWriteArrayList list= list.add("a"); list.add("b"); list.add("c"); // final ArrayList<String> testlist=new ArrayList<>(list);出错,ConcurrentModificationException错误 final CopyOnWriteArrayList<String> testlist=new CopyOnWriteArrayList<>(list); Thread thread = new Thread(new Runnable() { int count = -1; @Override public void run() { while(true){ testlist.add(count++ + ""); } } }); thread.setDaemon(true); //在启动了线程之后随着main方法执行完毕而终止,否则一直停在while循环 thread.start(); Thread.currentThread().sleep(3); for(String s: testlist){ System.out.println(s); System.out.println(testlist.hashCode()); }
换成CopyOnWriteArrayList后,就没有出现问题,然后就了解这个代码的内幕是什么样的,简单粗暴的原因就是去查看源码
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(); } }
final void setArray(Object[] a) { array = a; }
add方法中,使用了锁,然后就数组copy一份,将原来的数组引用指向新的数组
有优点就有缺点,缺点大概有这几方面
1.内存占用问题,因为要进行数组的复制,这样就多了一份拷贝在内存中,如果数据比较大的话,比如100M,复制就会成为200M,这样有可能会造成频繁的yongGC和fullGC;
2.实时一致性,CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
根据CopyOnWrite的实现机制,可以去实现CopyOnWriteMap,CopyOnWriteSet等等;这类主要应用场景为多读少写的并发情况,比如我们针对网站设置一个IP过滤器,每个IP进来的时候先查询是否在过滤器里面,如果存在就阻断访问,然后每天再将当天访问超过10000次的IP加入到过滤器里面。