并发编程笔记——第五章 Java并发包中并发List源码剖析

一、介绍

  • 并发包中的并发List只有CopyOnWriteArrayList,一个线程安全的ArrayList,修改操作是在底层的一个复制的数组(快照)上进行的(即写时复制策略)
  • 每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,RenntranLock独占锁对象用来保证同时只有一个线程对array进行修改

二、主要方法源码解析

围绕以下3点分析

  1. 何时初始化list,初始化元素个数,大小限制:调用构造器时,初始化长度为0,本质上维护一个数组
  2. 如何保证线程安全,比如多个线程进行读写时如何保证是线程安全的:采用独占锁和写时复制策略,保证了读写的弱一致性
  3. 如何保证使用迭代器遍历list时的数据一致性:修改操作采用写时复制策略,因此迭代器获取的array是一个快照版本,不会受到其它线程的写操作影响,保证了迭代器的弱一致性
  • 初始化
    // 无参构造器
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    // 有参构造器1
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
    
    // 有参构造器2
    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)为例:写时复制策略,保证了读写的弱一致性(需结合get方法理解)
    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();
        }
    }
  • 获取指定位置元素E get(int index):不上锁,这就解释了为什么add要复制一个快照(remove等其他写操作也会复制一个快照)
    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];
    }
  • 修改指定元素
    public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            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 {
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
  • 删除元素:要删谁不复制它就行了
    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();
        }
    }
  • 弱一致性的迭代器:同get方法,不上锁
    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 = initialCursor;
            snapshot = elements;
        }
        // 是否遍历结束
        public boolean hasNext() {
            return cursor < snapshot.length;
        }

        // 获取元素
        public E next() {
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }

       ...

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

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值