今天在项目中发现有人使用了CopyOnWriteArrayList,但是不明白这个类跟ArrayList有什么区别,于是搜索相关的内容学习一下:
CopyOnWriteArrayList是ArrayList的一个线程安全的变体,其中所有可变操作(add,set等)都是通过底层数组的进行一次新的复制产生的。
这一般需要很大的开销,但是当遍历操作的数量大大打的超过可变操作的数量时,这种方式更有效。在不能或者不想同步遍历的时候,但又要从并发线程中排出冲突的时候,他很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出ConCurrentModificationException.创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行元素的更改(remove,set和add)不受支持。这些方法将抛出UnsupportOpreationException。允许使用所有元素包括null.
内存一致性效果:当存在其他并发Collection的时候,讲对象放入CopyOnWriteArrayList之前的线程中的操作happen-before,随后通过另一线程从CopyOnWriteArayList中访问或移除该元素的操作。
这种情况一般出现在多线程操作时,一个线程堆list进行修改,一个线程对list进行读取时会出现java,util,ConCurrentModificationException错误。
/** * Project Name:flowbatch-web * File Name:CopyOnWriteArrayListDemo.java * Package Name:com.huateng.iccs.flowbatch.runner * Date:2018年3月1日下午2:10:32 * Copyright (c) 2018, [email protected] All Rights Reserved. * */ package com.huateng.iccs.flowbatch.runner; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * ClassName:CopyOnWriteArrayListDemo <br/> * Function: TODO ADD FUNCTION. <br/> * Reason: TODO ADD REASON. <br/> * Date: 2018年3月1日 下午2:10:32 <br/> * @author Administrator * @version * @since JDK 1.6 * @see */ public class CopyOnWriteArrayListDemo { /** * 读线程 * ClassName: ReadTask <br/> * Function: TODO ADD FUNCTION. <br/> * Reason: TODO ADD REASON(可选). <br/> * date: 2018年3月1日 下午2:15:08 <br/> * * @author Administrator * @version CopyOnWriteArrayListDemo * @since JDK 1.6 */ private class ReadTask implements Runnable{ private List<String> list; public ReadTask(List<String> list) { this.list=list; } @Override public void run() { for (String string : list) { System.out.println(string); } } } private class WriteTask implements Runnable{ private List<String> list ; private int index; public WriteTask(List<String> list, int index) { this.index=index; this.list=list; } @Override public void run() { list.remove(index); list.add(index, "write"+index); } } private void run() { int num =10; List<String> list = new ArrayList<String>(); for(int i=0;i<num;i++) { list.add("main"+i); } ExecutorService service = Executors.newFixedThreadPool(num); for (int i = 0; i <num; i++) { service.execute(new ReadTask(list)); service.execute(new WriteTask(list, i)); } } public static void main(String[] args) { new CopyOnWriteArrayListDemo().run(); } }
从结果中可以看出,多线程的情况下报错,如果换成copyonwriteArraylist做容器,这个类和ArrayList最大的区别就是add(E)的时候,容器会自动Copy一份出来然后在尾部add(E)。源码如下
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (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(); } }
用到了Arrays.copyO方法。这样导致每次操作都不是同一个引用。也就不会出现Java.util.ConcurrentModificationException错误。
换成如下代码
// List<String> list = new ArrayList<String>();
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
其结果没报错CopyOnWriteArrayList add(E)和remove(int index)都是对新的数组进行修改和新增,所以在多线程操作时不会出现报错,
所以得出的结论:CopyOnWriteArrayList适合使用在读取操作远远大于写操作的场景中,比如缓存。发生修改时做Copy,新老版本分离,保证读的高性能,适用于以读为主的情况
注:原文来自https://my.oschina.net/jielucky/blog/167198