CopyOnWriteArrayList定义
ArrayList本身不是线程安全的,在读线程读取ArrayList的数据时,此时在去写入数据,就会触发fast-fail
机制,抛出ConcurrentModificationException
异常。也可以使用Vector
去代替ArrayList
使用,或者使用Collections.synchronizeList()
包裹ArrayList。但他们都是使用synchronized
进行修饰,执行效率不高。
针对运行效率情况,有了CopyOnWriteArrayList
。
适用场景:读多写少 。
CopyOnWrite容器 CopyOnWrite
容器即写时复制
的容器。当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行copy,复制出一个新的容器,然后往新的容器添加元素,添加完元素之后,再将原容器的引用指向新的容器。
对CopyOnWrite容器
进行并发读的时候,不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器
也是一种读写分离 的思想,读和写采用不同的容器。放弃了数据实时性。
CopyOnWriteArrayList源码解析 重要参数分析 1 2 3 4 5 //利用Lock实现读写分离final transient ReentrantLock lock = new ReentrantLock(); //设置初始化数组引用private transient volatile Object[] array;
初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public CopyOnWriteArrayList() { //设置空数组 setArray(new Object[0]); } public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); }
插入数据 - add(E e) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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(); } }
采用ReentrantLock
,保证同一时刻只有一个线程正在进行数组的复制,否则的话内存中会有多份被复制的数据。
volatile
修饰的数组引用,在调用setArray()
时,线程对数组引用的修改是对其他线程可见的。
插入数据时插到新的数组中的,可以保证读和写操作在两个数组中执行,不会影响数据。
和ArrayList相比,效率比较低,每次插入一个数组 都需要进行数组复制操作,随着元素的增加,修改代价会越来越大。
获取数据 - get(int index) 1 2 3 4 5 6 7 8 public E get(int index) { //获取index对应数据 return get(getArray(), index); }private E get(Object[] a, int index) { return (E) a[index]; }
get()
没有添加线程安全控制,也没有加锁。因为get()操作的是旧数组,也不会发生修改操作。
移除数据 - remove(int index) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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(); } }
拓展 CopyOnWriteArrayList的缺点