【笔记】Java并发编程之美-Java并发包中并发List源码剖析

it2023-08-11  69

介绍

并发包中的并发List只有CopyOnWriteArrayList。CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)上。

每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,ReentrantLock独占锁对象用来保证同时只有一个线程对array进行修改。ReentrantLock是独占锁。

主要方法源码解析

初始化

无参CopyOnWriteArrayList(),入参为toCopyIn:CopyOnWriteArrayList(E[] toCopyIn),入参为集合:CopyOnWriteArrayList(Collections<? extends E> c)。

添加元素

有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)获取array 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(); } }
获取指定位置元素
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]; }

分为两步,先获取数组array,再通过下标index访问元素,此过程没有加锁同步。若另一个线程在本线程获取数组array之后和访问元素之前改变了数组,则出现了写时复制策略产生的弱一致性问题。

修改指定元素
public E set(int index, E element) { //获取独占锁 final ReentrantLock lock = this.lock; lock.lock() ; try { //获取array 和 旧值 Object[] elements= getArray() ; E oldValue = get(elements, index); //若旧值与新值不相等,创建新数组并复制元素,新数组的指定位置修改元素 if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len) ; newElements[index] =element; setArray(newElements) ; } else { //若相等,为了保证volatile语义,仍需重新设置array setArray(elements) ; } return oldValue; } }finally { //释放独占锁 lock.unlock(); } }
删除元素

使用 E remove(int index)、boolean remove(Object o)和 boolean remove(Object o, Object[] snapshot, int index)等方法,它们的原理一样。下面讲解remove(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 - l ; //若删除的是最后一个元素 if(numMoved == 0) setArray(Arrays.copyOf(elements, len - l)) ; 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(); } }
弱一致性的迭代器

所谓弱一致性是指返回迭代器后,其他线程对 list 的增删改对迭代器是不可见的。

总结

CopyOnWriteArrayList 使用写时复制的策略来保证 list 的一致性,而获取一修改一写入三步操作并不是原子性的,所以在增删改的过程中都使用了独占锁,来保证在某个时间只有一个线程能对 list 数组进行修改。另外 CopyOnWriteArrayList 提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对 list 的修改是不可见的,迭代器遍历的数组是一个快照。 另外,CopyOnWriteArraySet 的底层就是使用它实现的。

最新回复(0)