xiaobenchi

导航

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的修改是不可见的,迭代器遍历的数组是一个快照。

posted on 2022-07-30 16:18  小迟在努力  阅读(173)  评论(0)    收藏  举报