Java CopyOnWriteArrayList
Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。
从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。
什么是CopyOnWrite容器
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
源码:
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(); } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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(); } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public boolean addAll(int index, Collection<? extends E> c) { Object[] cs = c.toArray(); 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); if (cs.length == 0) return false; int numMoved = len - index; Object[] newElements; if (numMoved == 0) newElements = Arrays.copyOf(elements, len + cs.length); else { newElements = new Object[len + cs.length]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + cs.length, numMoved); } System.arraycopy(cs, 0, newElements, index, cs.length); setArray(newElements); return true; } finally { lock.unlock(); } }
set:
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 { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { lock.unlock(); } }
remove:
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(); } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public boolean remove(Object o) { Object[] snapshot = getArray(); int index = indexOf(o, snapshot, 0, snapshot.length); return (index < 0) ? false : remove(o, snapshot, index); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
private boolean remove(Object o, Object[] snapshot, int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); int len = current.length; if (snapshot != current) findIndex: { int prefix = Math.min(index, len); for (int i = 0; i < prefix; i++) { if (current[i] != snapshot[i] && eq(o, current[i])) { index = i; break findIndex; } } if (index >= len) return false; if (current[index] == o) break findIndex; index = indexOf(o, current, index, len); if (index < 0) return false; } Object[] newElements = new Object[len - 1]; System.arraycopy(current, 0, newElements, 0, index); System.arraycopy(current, index + 1, newElements, index, len - index - 1); setArray(newElements); return true; } finally { lock.unlock(); } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void removeRange(int fromIndex, int toIndex) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (fromIndex < 0 || toIndex > len || toIndex < fromIndex) throw new IndexOutOfBoundsException(); int newlen = len - (toIndex - fromIndex); int numMoved = len - toIndex; if (numMoved == 0) setArray(Arrays.copyOf(elements, newlen)); else { Object[] newElements = new Object[newlen]; System.arraycopy(elements, 0, newElements, 0, fromIndex); System.arraycopy(elements, toIndex, newElements, fromIndex, numMoved); setArray(newElements); } } finally { lock.unlock(); } }
===============================================================================
多线程对集合进行操作:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) { new CopyOnWriteArrayListDemo().run(); } } class CopyOnWriteArrayListDemo { /** * 读线程 * * @author wangjie */ private static class ReadTask implements Runnable { List<String> list; public ReadTask(List<String> list) { this.list = list; } public void run() { for (String str : list) { System.out.println(str); } } } /** * 写线程 * * @author wangjie */ private static class WriteTask implements Runnable { List<String> list; int index; public WriteTask(List<String> list, int index) { this.list = list; this.index = index; } public void run() { list.remove(index); list.add(index, "write_" + index); } } public void run() { final int NUM = 5; List<String> list = new ArrayList<String>(); //List<String> list=new CopyOnWriteArrayList<String>(); for (int i = 0; i < NUM; i++) { list.add("main_" + i); } ExecutorService executorService = Executors.newFixedThreadPool(NUM); for (int i = 0; i < NUM; i++) { executorService.execute(new ReadTask(list)); executorService.execute(new WriteTask(list, i)); } executorService.shutdown(); } }
main_0 Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-5" Exception in thread "pool-1-thread-3" java.util.ConcurrentModificationException main_1 write_0 main_1 write_0 write_0 write_0 write_1 write_2 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) write_3 at java.util.ArrayList$Itr.next(ArrayList.java:851) main_4 at com.qhong.CopyOnWriteArrayListDemo$ReadTask.run(Main.java:28) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.qhong.CopyOnWriteArrayListDemo$ReadTask.run(Main.java:28) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.qhong.CopyOnWriteArrayListDemo$ReadTask.run(Main.java:28) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.qhong.CopyOnWriteArrayListDemo$ReadTask.run(Main.java:28) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
把注释的取消,换成CopyOnWriteArrayList
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
main_0
main_1
main_2
main_3
main_4
main_0
write_1
write_2
main_3
main_4
write_0
write_1
write_2
write_3
write_0
write_1
write_2
main_4
write_4
write_0
write_1
write_2
write_3
write_4
=============================================================================================
Array一边遍历,一边删除
foreach:
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { String[] strArray={"111","222","333","444"}; //System.out.println(Arrays.toString(strArray)); List<String> list=new ArrayList<String>(); list.addAll(Arrays.asList(strArray)); System.out.println(list); for(String str:list){ System.out.println(str); list.remove(str); System.out.println("移除后:"+list); } } }
[111, 222, 333, 444] Exception in thread "main" java.util.ConcurrentModificationException 111 移除后:[222, 333, 444] at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.qhong.Main.main(Main.java:15) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
发现报错
for:
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { String[] strArray={"111","222","333","444"}; //System.out.println(Arrays.toString(strArray)); List<String> list=new ArrayList<String>(); list.addAll(Arrays.asList(strArray)); System.out.println(list); for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); list.remove(i); //list.remove(list.get(i)); System.out.println(list); } } }
[111, 222, 333, 444] 111 [222, 333, 444] 333 [222, 444]
不报错,但是跟我们想要的不一样,删一个,漏一个
迭代器:
import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; public class Main { public static void main(String[] args) { String[] strArray={"111","222","333","444"}; //System.out.println(Arrays.toString(strArray)); List<String> list=new ArrayList<String>(); list.addAll(Arrays.asList(strArray)); System.out.println(list); Iterator<String> it = list.iterator(); while(it.hasNext()){ it.next(); it.remove(); System.out.println(list); } } }
[111, 222, 333, 444] [222, 333, 444] [333, 444] [444] []
可以使用。
CopyOnWriteArrayList:
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { String[] strArray={"111","222","333","444"}; List<String> list=new ArrayList<String>(); list.addAll(Arrays.asList(strArray)); System.out.println(list); CopyOnWriteArrayList<String> list2=new CopyOnWriteArrayList<String>(list); System.out.println(list2); for(String item:list2){ list2.remove(item); System.out.println(list2); } } }
[111, 222, 333, 444] [111, 222, 333, 444] [222, 333, 444] [333, 444] [444] []
可以正常使用
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { String[] strArray={"111","222","333","444"}; List<String> list=new ArrayList<String>(); list.addAll(Arrays.asList(strArray)); System.out.println(list); CopyOnWriteArrayList<String> list2=new CopyOnWriteArrayList<String>(list); System.out.println(list2); for(int i=0;i<list2.size();i++) { String item=list2.get(i); list2.remove(item); System.out.println(list2); } } }
[111, 222, 333, 444] [111, 222, 333, 444] [222, 333, 444] [222, 444]
跟ArrayList结果一样。
理想的应该这样:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { String[] strArray={"111","222","333","444"}; List<String> list=new ArrayList<String>(); list.addAll(Arrays.asList(strArray)); System.out.println(list); CopyOnWriteArrayList<String> list2=new CopyOnWriteArrayList<String>(list); System.out.println(list2); for(int i=0;i<list2.size();) { String item=list2.get(i); list2.remove(item); System.out.println(list2); } } }
总的对于一边遍历一边remove来说,foreach这种的有问题。
==================================================================
CopyOnWriteArrayList一边遍历,一遍添加
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { String[] strArray={"111","222","333","444"}; List<String> list=new ArrayList<String>(); list.addAll(Arrays.asList(strArray)); System.out.println(list); CopyOnWriteArrayList<String> list2=new CopyOnWriteArrayList<String>(list); System.out.println(list2); for(int i=0;i<list2.size();i++) { if(i<6) { String item = list2.get(i); list2.add("sss"); System.out.println(list2); } } } }
[111, 222, 333, 444] [111, 222, 333, 444] [111, 222, 333, 444, sss] [111, 222, 333, 444, sss, sss] [111, 222, 333, 444, sss, sss, sss] [111, 222, 333, 444, sss, sss, sss, sss] [111, 222, 333, 444, sss, sss, sss, sss, sss] [111, 222, 333, 444, sss, sss, sss, sss, sss, sss]
foreach
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { String[] strArray={"111","222","333","444"}; List<String> list=new ArrayList<String>(); list.addAll(Arrays.asList(strArray)); System.out.println(list); CopyOnWriteArrayList<String> list2=new CopyOnWriteArrayList<String>(list); System.out.println(list2); for(String item:list2) { list2.add("sss"); System.out.println(list2); } } }
[111, 222, 333, 444] [111, 222, 333, 444] [111, 222, 333, 444, sss] [111, 222, 333, 444, sss, sss] [111, 222, 333, 444, sss, sss, sss] [111, 222, 333, 444, sss, sss, sss, sss]
发现没有对foreach遍历产生影响。
迭代器:
import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { String[] strArray={"111","222","333","444"}; List<String> list=new ArrayList<String>(); list.addAll(Arrays.asList(strArray)); System.out.println(list); CopyOnWriteArrayList<String> list2=new CopyOnWriteArrayList<String>(list); System.out.println(list2); Iterator<String> it = list2.iterator(); while(it.hasNext()){ it.next(); list2.add("sss"); System.out.println(list2); } } }
[111, 222, 333, 444] [111, 222, 333, 444] [111, 222, 333, 444, sss] [111, 222, 333, 444, sss, sss] [111, 222, 333, 444, sss, sss, sss] [111, 222, 333, 444, sss, sss, sss, sss]
CopyOnWrite的缺点
CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。
内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象 。
如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。
针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如ConcurrentHashMap.
数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
http://www.cnblogs.com/dolphin0520/p/3938914.html
http://www.cnblogs.com/alipayhutu/archive/2012/08/11/2634073.html