参考链接:
Copy-On-Write简称COW,当要修改某个集合时,进行一份集合拷贝,修改新拷贝后的集合,完成后再将新集合指向旧集合。
查看CopyOnWriteArrayList源码
add方法
使用final ReentrantLock lock = this.lock;
lock.lock();
在调用add(...)方法前先加锁,保证同一时间只能有一个线程在添加元素。使用Arrays.copyOf(...)方法复制出另一个新的数组,而且新的数组的长度比原来多1,拷贝完成后,将新添加的元素赋值给新数组,最后把新的副本数组赋值给旧的数组,然后在finally中释放锁。
remove方法
删除元素,主要判断是不是删除最后一个元素,如果是的话,直接复制原来数组的length-1,否则先复制数组index前面到新数组,然后再复制index后面的元素到数组中,最后再把新数组赋值给旧数组的引用。
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). Returns the element that was removed from the list. * * @throws IndexOutOfBoundsException {@inheritDoc} */ 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) // 判断index是不是最后一个,举例如果原来数组总共有3个元素,len 是 3, index 是 2, 则numMoved是0; setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; // 拷贝一个比原来数组长度少一个的数组 System.arraycopy(elements, 0, newElements, 0, index); // 先拷贝index前半部分, System.arraycopy(elements, index + 1, newElements, index, // 再拷贝index+1后面的部分 numMoved); setArray(newElements); // 最后将新数组的引用赋值给旧数组 } return oldValue; } finally { lock.unlock(); } }
其次看System.arraycopy(...)方法的源码
get方法
set方法
代码示例:
多线程异常也不是一定会出现的,但使用ArrayList有很大机会会出现并发异常,下面改用CopyOnWriteArrayList可以解决这个问题。如果使用ArrayList没有出现并发异常,建议把线程池改大点。
package com.jerry.entity;import java.util.ArrayList;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class CopyOnWriteListTest { private static final int THREAD_POOL_MAX_NUM = 10;// private ListmList = new ArrayList (); private List mList = new CopyOnWriteArrayList (); public static void main(String[] args) { new CopyOnWriteListTest().start(); } private void initData() { this.mList.add("code_99"); this.mList.add("code_98"); this.mList.add("code_97"); } private void start() { initData(); ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_MAX_NUM); for(int i=1; i<=THREAD_POOL_MAX_NUM; i++) { executorService.execute(new ListReader(this.mList)); String codeId = "code_"+i; executorService.execute(new ListWriter(this.mList, codeId)); } executorService.shutdown(); } private class ListReader implements Runnable { private List rList; public ListReader(List list) { this.rList = list; } @Override public void run() { if(this.rList != null) { System.out.println("Reader Thread: "+Thread.currentThread().getName()+" --> "+this.rList.toString()); } } } private class ListWriter implements Runnable { private List wList; private String codeId; public ListWriter(List list, String codeId) { this.wList = list; this.codeId = codeId; } @Override public void run() { if(this.wList != null) { this.wList.add(codeId); System.out.println("Writer Thread: "+Thread.currentThread().getName()+" --> "+this.wList.toString()); } } }}
CopyOnWriteArrayList的优点和缺点:
优点:解决多线程的并发问题
缺点:
1.内存占用有问题:写时拷贝,内存中会存在两份对象。很明显两个数组同时驻扎在内存中,如果实际应用中,数据比较多,占用内存会比较大,针对这个可以用ConcurrentHashMap来代替。因此,不建议对大对象使用CopyOnWriteArrayList。
ConcurrentHashMap:
2. 数据的一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。因此对实时性要求的数据,不建议使用COW。