【从入门到放弃-Java】并发编程-JUC-CopyOnWriteArrayList-阿里云开发者社区

开发者社区> aloof_> 正文

【从入门到放弃-Java】并发编程-JUC-CopyOnWriteArrayList

简介: 前言 上文【从入门到放弃-Java】并发编程-JUC-ConcurrentHashMap中,我们学习了常用的并发容器CurrentHashMap,本文我们来了解下List的并发容器:CopyOnWriteArrayList直接来看源码。
+关注继续查看

前言

上文【从入门到放弃-Java】并发编程-JUC-ConcurrentHashMap中,我们学习了常用的并发容器CurrentHashMap,本文我们来了解下List的并发容器:CopyOnWriteArrayList
直接来看源码。

CopyOnWriteArrayList

add

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    //使用synchronized加锁
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        //拷贝一份array,数组大小加一
        es = Arrays.copyOf(es, len + 1);
        //设置最后一位为需要添加的值e
        es[len] = e;
        //将新的array设置为当前List的中的值,array是volatile类型的,因此写入后其它线程能立即看到
        setArray(es);
        return true;
    }
}

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    //使用synchronized加锁
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException(outOfBounds(index, len));
        Object[] newElements;
        int numMoved = len - index;
        
        if (numMoved == 0)
            //如果是在尾部插入,则将List中的数组直接copy并将长度加一
            newElements = Arrays.copyOf(es, len + 1);
        else {
            //如果不是在尾部插入,则以index处将原array分割成两部分copy到新数组中,空出index的位置
            newElements = new Object[len + 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index, newElements, index + 1,
                             numMoved);
        }
        //在index出设置element的值
        newElements[index] = element;
        setArray(newElements);
    }
}

addAll

/**
 * Appends all of the elements in the specified collection to the end
 * of this list, in the order that they are returned by the specified
 * collection's iterator.
 *
 * @param c collection containing elements to be added to this list
 * @return {@code true} if this list changed as a result of the call
 * @throws NullPointerException if the specified collection is null
 * @see #add(Object)
 */
public boolean addAll(Collection<? extends E> c) {
    //获取数组
    Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
        ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
    if (cs.length == 0)
        return false;
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        Object[] newElements;
        
        if (len == 0 && cs.getClass() == Object[].class)
            //如果当前List长度为0,则直接设置array为新数组
            newElements = cs;
        else {
            //将原数组copy为新的数组,新的数组长度为len+cs.lenth
            newElements = Arrays.copyOf(es, len + cs.length);
            //从len处开始,设置为需要添加的数组
            System.arraycopy(cs, 0, newElements, len, cs.length);
        }
        //将值写入List的成员变量array,array是volatile类型的,因此写入后其它线程能立即看到
        setArray(newElements);
        return true;
    }
}

/**
 * Inserts all of the elements in the specified collection into this
 * list, starting at the specified position.  Shifts the element
 * currently at that position (if any) and any subsequent elements to
 * the right (increases their indices).  The new elements will appear
 * in this list in the order that they are returned by the
 * specified collection's iterator.
 *
 * @param index index at which to insert the first element
 *        from the specified collection
 * @param c collection containing elements to be added to this list
 * @return {@code true} if this list changed as a result of the call
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws NullPointerException if the specified collection is null
 * @see #add(int,Object)
 */
public boolean addAll(int index, Collection<? extends E> c) {
    //获取数组
    Object[] cs = c.toArray();
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException(outOfBounds(index, len));
        if (cs.length == 0)
            //如果当前List长度为0,直接返回false插入失败
            return false;
        int numMoved = len - index;
        Object[] newElements;
        if (numMoved == 0)
            //如果List尾部插入,则直接在尾部copy要插入的数组
            newElements = Arrays.copyOf(es, len + cs.length);
        else {
            //如果不是在尾部插入,则以index处将原array分割成两部分copy到新数组中,空出index到index+cs.length长度的位置
            newElements = new Object[len + cs.length];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index,
                             newElements, index + cs.length,
                             numMoved);
        }
        //将index到index+cs.length填充需要插入的数组
        System.arraycopy(cs, 0, newElements, index, cs.length);
        setArray(newElements);
        return true;
    }
}

get

//get方式就比较简单了 因为array是volatile类型的,不需要任何同步操作就可取到值,保证了并发
public E get(int index) {
    return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
    return (E) a[index];
}

remove

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).  Returns the element that was removed from the list.
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    //同步锁
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        E oldValue = elementAt(es, index);
        int numMoved = len - index - 1;
        Object[] newElements;
        if (numMoved == 0)
            //如果是移除最后一个元素,则直接copy 0到len-2位置的元素即可
            newElements = Arrays.copyOf(es, len - 1);
        else {
            //如果不是最后一个元素,则copy需要删除数组[0,index)和[index,length]的元素到新数组。
            newElements = new Object[len - 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index + 1, newElements, index,
                             numMoved);
        }
        setArray(newElements);
        return oldValue;
    }
}

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If this list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * {@code i} such that {@code Objects.equals(o, get(i))}
 * (if such an element exists).  Returns {@code true} if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *
 * @param o element to be removed from this list, if present
 * @return {@code true} if this list contained the specified element
 */
public boolean remove(Object o) {
    Object[] snapshot = getArray();
    //找到需要删除元素的索引
    int index = indexOfRange(o, snapshot, 0, snapshot.length);
    return index >= 0 && remove(o, snapshot, index);
}

/**
 * A version of remove(Object) using the strong hint that given
 * recent snapshot contains o at the given index.
 */
private boolean remove(Object o, Object[] snapshot, int index) {
    synchronized (lock) {
        Object[] current = getArray();
        int len = current.length;
        //加锁后验证数组是否在找到要删除的索引后改动过,如果改动的话,删除改动后的第一个o元素
        if (snapshot != current) findIndex: {
            int prefix = Math.min(index, len);
            for (int i = 0; i < prefix; i++) {
                if (current[i] != snapshot[i]
                    && Objects.equals(o, current[i])) {
                    index = i;
                    break findIndex;
                }
            }
            if (index >= len)
                return false;
            if (current[index] == o)
                break findIndex;
            index = indexOfRange(o, current, index, len);
            if (index < 0)
                return false;
        }
        //删除index位置的元素
        Object[] newElements = new Object[len - 1];
        System.arraycopy(current, 0, newElements, 0, index);
        System.arraycopy(current, index + 1,
                         newElements, index,
                         len - index - 1);
        setArray(newElements);
        return true;
    }
}

iterator

/**
 * Returns an iterator over the elements in this list in proper sequence.
 *
 * <p>The returned iterator provides a snapshot of the state of the list
 * when the iterator was constructed. No synchronization is needed while
 * traversing the iterator. The iterator does <em>NOT</em> support the
 * {@code remove} method.
 *
 * @return an iterator over the elements in this list in proper sequence
 */
public Iterator<E> iterator() {
    //和ArrayList不同,CopyOnWriteArrayList在iterator遍历时,是对当前的array做了一个快照,在遍历期间,array可能会被别的线程更改,但快照不会改变,不受影响,因此在迭代中,数组元素不能改。
    return new COWIterator<E>(getArray(), 0);
}

总结

通过源码分析我们了解到,CopyOnWriteArrayList写操作时,都是以加synchronized锁并copy一份数组进行修改的方式进行的,如果List比较大时,会非常占用资源。

读操作时不用加锁,因为array是volatile的,不需要额外同步,因此读性能非常高。

因此CopyOnWriteArrayList更适用于读多写少的并发操作中。

在遍历时,将当前的array做一个快照,不受其他线程更改的影响。因此,在iterator中,list中的元素是不能更改的(因为更改的是快照,不会写到原数组中,更改一定是无效的)。

但在for循环时,CopyOnWriteArrayList中的元素可以被更改,而ArrayList不行,因为ArrayList的元素被遍历到时总会先检查是否被更改,更改会抛出ConcurrentModificationException

更多文章

见我的博客:https://nc2era.com

written by AloofJr,转载请注明出处

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
# Apache Spark系列技术直播# 第五讲【 Spark RDD编程入门 】
内容提要:本次讲座主要涵盖Spark RDD编程入门基础,包括:1. Spark、RDD简介 2. RDD API简介 3. 打包与spark-submit 4. 性能分析与调优基础主讲人:王道远(健身) 阿里巴巴计算平台EMR技术专家直播时间:2018.
1235 0
站着编程两年后我身体上的变化
自从我使用站立式电脑桌工作以来已经有2年时间。不论一天要编程多少个小时,我都是站立在电脑前。也就是说,有些日子我会一天站立超过10个小时,虽然不是连续的——中间会有小憩,吃饭,冥想等。
41 0
+关注
aloof_
前阿里巴巴集团UC事业群服务端研发,现蚂蚁金服技术风险部服务端研发。擅长语言Java、PHP,对MySQL兴趣浓厚,热爱技术,热爱分享
38
文章
0
问答
来源圈子
更多
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载