CopyOnWriteArrayList分析

  CopyOnWriteArrayList是一个并发集合类,它实现了List接口,它是一个线程安全类而且它实现线程安全的机制和Vector不一样。Java API文档说它是通过复制一个内部数组来实现写操作(包括add、set等等)的线程安全机制。这种方式会很耗费性能,但是在遍历操作次数远大于写操作的时候效率会很高。这种复制模式称为“snapshot style”,当调用iterator方法创建迭代器时,迭代器保留一个指向底层数组的引用,在迭代器的生命周期内,这个数组永远不会改变,并且迭代器不支持写操作,会产生UnsupportedOperationException异常。

  来看看代码:

1 /** The lock protecting all mutators */
2     transient final ReentrantLock lock = new ReentrantLock();
3 
4     /** The array, accessed only via getArray/setArray. */
5     private volatile transient Object[] array;

从代码中可以看出,CopyOnWriteArrayList用关键字volatile修饰了数组array,这样可以确保每次遍历list时取得的数组内容都是最新的。另外还定义了一个ReentrantLock类型的lock,用作写操作时的加锁用。

  来看看写操作的代码:

/**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        Object oldValue = 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 (E)oldValue;
    } finally {
        lock.unlock();
    }
    }

在这个set方法,首先会取得lock,lock.lock(),然后定义oldValue赋值为替换前的值,接着赋值一个数组newElements,将这个新数组的对应下标的值替换为新的值。最后调用setArray方法,把底层数组替换为新数组。最后再释放lock,lock.unlock()。如果在这中间我们调用了iterator方法获取了迭代器,那么迭代器用到的数组就不是新数组,还是老的数组,迭代器如果要取得最新的数组,则必须再调用iterator方法。

看个例子:

 1 public static void main(String[] args) {
 2         CopyOnWriteArrayList list = new CopyOnWriteArrayList();
 3         list.add(1);
 4         list.add(2);
 5         list.add(3);
 6         Iterator it = list.iterator();
 7         list.add(4);
 8         while (it.hasNext()) {
 9             int i = (Integer) it.next();
10             System.out.println(i);
11         }
12         System.out.println(list);
13     }

这个方法的输出是:

1 1
2 2
3 3
4 [1, 2, 3, 4]

足以证明它们操作的不是同一个数组。

 

posted @ 2013-07-25 21:07  画水  阅读(282)  评论(0编辑  收藏  举报