今天在项目中发现有人使用了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, chenzhou1025@126.com 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