Java并发包中并发List源码剖析
Java并发包中并发List源码剖析
1. 介绍
并发包中的并发List只有CopyOnWriteArrayList。CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略。
-
CopyOnWriteArrayList的类图结构
![image-20220730145444390]()
每个CopyOnWirteArrayList对象里面有一个array数组对象来存放具体元素,ReentrantLock独占锁对象用来保证同时只有一个线程对array进行修改。
2. 主要方法源码解析
-
初始化
在内部创建了一个大小为0的Object数组作为array的初始值
public CopyOnWriteArrayList(){ setArray(new Object[0]); }有参构造函数
//创建一个list,其内部元素是入参toCopyIn的副本 public CopyOnWriteList(E[] toCopyIn){ setArray(Arrays.copyOf(toCopyIn,toCopyIn.length,Object[].class)); } //入参为集合,将集合里面的元素复制到本list 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[] if(elements.getClass() != Object[].class){ elements = Arrays.copyOf(elements,elements.length,Object[].class); } } setArray(elements); } -
添加元素
CopyOnWriteArrayList中用来添加元素的函数有add(E e)、add(int index,E element)、addIfAbsent(E e)和addAllAbsent(Collection<?extends E> c)等,它们的原理类似,以add(E e)为例来讲解。
public boolean add(E e){ //获取独占锁(1) final ReentrantLock lock = this.lock; lock.lock(); try{ //(2)获取 Object[] elements = getArray(); //(3)复制array到新数组,添加元素到新数组 int len = elements.length; Object[] newElements = Arrays.copyOf(elements,len+1); newElements[len] = e; //(4)使用新数组替换添加以前的数组 setArray(newElements); return true; }finally{ //(5)释放独占锁 lock.unlock(); } } -
获取指定位置上的元素
使用E get(int index)获取下标为index的元素,如果元素不存在则抛出IndexOutOfBoundsException异常。
public E get(int index){ return get(getArray(),index); } final Object[] getArray(){ return array; } private E get(Object[] a, int index){ return (E) a[index]; }写时复制策略会产生弱一致性问题。
-
修改指定元素
使用E set(int index,E element)修改list中指定元素的值,如果指定位置的元素不存在则抛出IndexOutOfBoundsException异常。
public E set(int index, E element){ final ReentrantLock lock = this.lock; lock.lock(); try{ Object[] elements = getArray(); E oldValue = get(elements,index); if(oldVlaue != elements){ int len = elements.length; Object[] newElements = Arrays.copyOf(elements,len); newElements[index] = element; setArray(newElements); }else{ setArray(elements); } return oldValue; }finally{ lock.unlock(); } } -
删除元素
删除list里面指定的元素,可以使用E remove(int index)、boolean remove(Object o)和boolean remove(Object o,Object[] snapshot,int index)等方法,它们的原理一样。
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(numMove == 0){ setArray(Arrays.copyOf(elements,len - 1)); }else{ //分两次复制删除后剩余的元素到新数组 Object[] newElements = new Object[len - 1]; System.arraycopy(elements,0,newElement,0,index); System.arraycopy(elements,index + 1,newElements,index,numMoved); //使用新数组代替老数组 setArray(newElements); } return oldValue; }finally{ lock.unlock(); } } -
弱一致性的迭代器
遍历列表元素可以使用迭代器。
迭代器的hasNext方法用于判断列表中是否还有元素,next方法则具体返回元素。
![]()
所谓弱一致性是指返回迭代器后,其他线程对list的增删改对迭代器是不可见的
public Iterator<E> iterator(){ return new COWIterator<E>(getArray(),0); } static final class COWIterator<E> implements ListIterator<E>{ //array的快照版本 private final Object[] snapshot; //数组下标 private int cursor; //构造函数 private COWIterator(Object[] elements, int initialCursor){ cursor = initiaCursor; snapshot = elements; } //是否遍历结束 public boolean hasNext(){ return cursor < snapshot.length; } //获取元素 public E next(){ if(! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } }
3. 总结
CopyOnWriteArrayList使用写时复制的策略来保证list的一致性,而获取—修改—写入三步操作并不是原子性的,所以在增删改的过程中都使用了独占锁,来保证在某个时间只有一个线程能对list数组进行修改。另外CopyOnWriteArrayList提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对list的修改是不可见的,迭代器遍历的数组是一个快照。


浙公网安备 33010602011771号