CopyOnWriteArrayList源码分析整理

有关并发,前面已经整理了十几篇,下面结合并发和容器说说。

之前在讲容器类的时候提到过List和java.util.ArrayList,这些实现大多没有更多地考虑并发的情况,比如迭代器Iterator的使用,就有可能会抛出并发修改异常。在多线程高并发情况下,使用传统的java.util.ArrayList是不能保证线程安全的。在java.util.concurrent包中,有一种同样基于数组实现的List保证了线程安全。这个实现类就是CopyOnWriteArrayList,这篇文章就简要分析和整理下

0. “CopyOnWrite”简介

本人在学Linux习操作系统进程/线程的时候就听到过这个概念,直接翻译过来叫“写时拷贝”,说的是当需要创建一个新线程,会做fork()调用,而这时并未真正意义上开辟一块新的线程内存资源,仅当线程有“写操作”时,才会拷贝父线程的数据作为新线程的初始内容。

而在JavaSE5的并发包中,CopyOnWriteArrayList的实现也是利用了类似的方法。当数组内容有所变化时,拷贝一份新的出来,并把对象引用赋值给List的属性,旧数组的访问也依然有效,不会受到新的操作干扰。

容器对象的改动有很多种,对于CopyOnWriteArrayList每发生一次改变,CopyOnWriteArrayList就会复制一份数据,显然这个也是需要成本的。所以,CopyOnWriteArrayList其实是更适用于读操作远远多于写操作的场景。

public class CopyOnWriteArrayList
    implements List, RandomAccess, Cloneable, java.io.Serializable

但即使有了CopyOnWrite,在写操作时,创建新数组对象,并将其引用赋值时,仍然是会受到多线程的影响,这个还是要通过锁来保护。

transient final ReentrantLock lock = new ReentrantLock();

这就是CopyOnWriteArrayList类中的锁属性。

1. 读操作

对于读操作来讲,是不需要加锁的,直接读取引用对应的对象即可。因为即使数组有所改动,也是开辟新的数组并在新的数组中做修改,旧数组对象是始终可用的。

下面是get()方法的调用:

    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    public E get(int index) {
        return get(getArray(), index);
    }

其中public的get()方法是我们去调用的,而这个方法本身又会调用一个private的双参get()方法,最终实现就是返回数组a的第index元素(从0算起)。

getArray()的方法则是简单的返回CopyOnWriteArrayList的实现数组对象属性。

再稍微说下迭代器Iterator的使用。CopyOnWriteArrayList内部给出了COWIterator类的实现,也非常简单,只有数组snapshot属性和索引cursor属性。snapshot的名字定义得非常好,意为“快照”。实际上CopyOnWriteArrayList的迭代器对象就是在生成时的那一个快照,后续的迭代读操作都是在这个快照基础上进行的。

2. 写操作

在这篇文章的前面已经提到了,CopyOnWriteArrayList中是有ReentrantLock类的锁对象属性的。在修改CopyOnWriteArrayList类的对象时,是需要考虑多个线程访问array对象的。那么,锁的使用也就是必须的了。

在CopyOnWriteArrayList中,CopyOnWrite的过程通常是这样进行的。先在原数组对象属性的基础上copy出一个新的数组,其它改动操作都在新的数组对象上完成,当所有修改操作完成后,将新数组对象的引用交给CopyOnWriteArrayList类的array属性对象。整个过程在锁的保护下进行。

以add()方法为例,做一下代码分析:

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

其实整个过程和上面所说都是一致的。

除了add()以外,CopyOnWriteArrayList类还有很多其它修改操作,或者叫写操作。无论修改多复杂都和add()方法的流程一样,先加锁,拷贝一份新数组对象,在新的对象上改动,赋值回去,解锁。

在java.util.concurrent包中,除了CopyOnWriteArrayList类,也还有这样一个类CopyOnWriteArraySet。CopyOnWriteArraySet的实现是完全基于CopyOnWriteArrayList的。更详细的大家可以参看JDK,欢迎补充。

此条目发表在 Java, Java语言, 并发, 开发, 计算机技术 分类目录,贴了 , , , , , 标签。将固定链接加入收藏夹。

CopyOnWriteArrayList源码分析整理》有 12 条评论

  1. xiaoyukid 说:

    既然加锁了,为什么还要copy?

  2. xiaoyukid 说:

    是为了迭代的时候 不抛异常?

  3. 最励志网 说:

    最励志网:http://www.zuilizhi.net 前来拜访,欢迎互访!

  4. 网站不错很漂亮,欢迎互访!

  5. 内涵笑话 说:

    很好的网站,赞一个,加油!

  6. 家具电商 说:

    [ali掀桌子] 太厉害啦!值得我们学习

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>