码迷,mamicode.com
首页 > 其他好文 > 详细

CopyOnWriteArrayList源码阅读

时间:2018-11-15 19:45:11      阅读:134      评论:0      收藏:0      [点我收藏+]

标签:VID   call   dex   随机   boolean   顺序   str   快照   state   

1、CopyOnWrite容器有两种:
·CopyOnWriteArrayList
·CopyOnWriteArraySet
CopyOnWrite容器简称COW容器,其特点如下:
1)CopyOnWrite容器即写时复制的容器。
2)通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器,读时都是访问旧容器,写时才需要加锁。
3)这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
2、CopyOnWriteArrayList类继承结构:
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable;

3、对于CopyOnWriteArrayList主要查看以下几个方法:
·创建:CopyOnWriteArrayList()
·添加元素:即add(E)方法
·获取单个对象:即get(int)方法
·删除对象:即remove(E)方法
·遍历所有对象:即iterator(),在实际中更常用的是增强型的for循环去做遍历。

4、构造方法CopyOnWriteArrayList,其相关代码:
/* 只能 getArray/setArray访问数组. /
private volatile transient Object[] array;

/**
 * 获取数组
 */
final Object[] getArray() {
    return array;
}

/**
 * 设置数组
 */
final void setArray(Object[] a) {
    array = a;
}

/**
 *创建一个空的数组,Object[0],而ArrayList则是10
 */
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

5、添加元素
public boolean add(E e) {

    final ReentrantLock lock = this.lock;
    lock.lock(); //获取全局锁(独占锁),获取不到则阻塞
    try {
        Object[] elements = getArray(); //获取当前的数组
        int len = elements.length;
                     /*
         * Arrays.copyOf(elements, len + 1)的大致执行流程:
         * 1)创建新数组,容量为len+1,
         * 2)将旧数组elements拷贝到新数组,
         * 3)返回新数组
         */
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e; //新数组的末尾元素设成e
        setArray(newElements); //将旧数组指向新数组引用
        return true;
    } finally {
        lock.unlock();
    }
}

    6、获取指定元素

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

/**
 * {@inheritDoc}
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}  //找不到指定的元素则抛出异常
 */
public E get(int index) {
    return get(getArray(), index); //先获取数组,在读取指定位置的元素。
}

        从以上代码可以看出,读取元素时不加锁的,此时采用的是弱一致性策略。获取指定位置的元素分为两步,首先获取到当前list里面的array数组,这里称为步骤1,然后通过随机访问的下标方式访问指定位置的元素,这里称为步骤2。
        因为整个过程并没有加锁,这就可能会导致当执行完步骤1后执行步骤2前,另外一个线程C进行了修改操作,比如remove操作,就会进行写时拷贝删除当前get方法要访问的元素,并且修改当前list的array为新数组。而这之后步骤2 可能才开始执行,步骤2操作的是线程C删除元素前的一个快照数组(因为步骤1让array指向的是原来的数组),所以虽然线程C已经删除了index处的元素,但是步骤2还是返回index处的元素,这其实就是写时拷贝策略带来弱一致性。

7、修改指定元素,若元素不存在则抛出ndexOutOfBoundsException。
/**

  • 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();
    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();
    }

    }
    如果指定位置元素与新值一样,则为了保障volatile语义,还是需要重新设置下array,虽然array内容并没有改变(为了保证 volatile 语义是考虑到 set 方法本身应该提供 volatile 的语义).。

    7、删除元素
        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];
                          //分两次拷贝删除后的数组
                            //先复制index位置之前的元素,
            System.arraycopy(elements, 0, newElements, 0, index); //index在此处表示要复制的长度或元素个数
                            //复制inex位置之后的元素
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
                            //其中:src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度。
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock(); //操作完成,解除锁定
    }

    }

    8、弱一致性的迭代器

    /**

  • 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
  • <tt>remove</tt> method. 大概意思就是:返回的迭代器是list列表的一个快照,在整个迭代的过程中不需要同步(加锁),这个迭代器不支持remove操作。
  • @return an iterator over the elements in this list in proper sequence
    */
    public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
    }

    /**

  • {@inheritDoc}
  • <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
  • <tt>remove</tt>, <tt>set</tt> or <tt>add</tt> methods. 不支持remove/set/add等操作
    */
    public ListIterator<E> listIterator() {
    return new COWIterator<E>(getArray(), 0);
    }

    /**

  • {@inheritDoc}
  • <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
  • <tt>remove</tt>, <tt>set</tt> or <tt>add</tt> methods.
  • @throws IndexOutOfBoundsException {@inheritDoc}
    */
    public ListIterator<E> listIterator(final int index) {
    Object[] elements = getArray();
    int len = elements.length;
    if (index<0 || index>len)
    throw new IndexOutOfBoundsException("Index: "+index);

    return new COWIterator<E>(elements, index);

    }

    private static class COWIterator<E> implements ListIterator<E> {
    /* Snapshot of the array /array数组的一个快照
    private final Object[] snapshot;
    /* Index of element to be returned by subsequent call to next. /将由后续调用next返回的元素的索引。即数组下标或索引
    private int cursor;

    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }
    
    //判断是否有下个元素
    public boolean hasNext() {
        return cursor < snapshot.length;
    }
    
            ......
    //获取当前元素,索引加1
    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
            .....

    }

    这里为什么说snapshot是list的快照呢?明明是指针传递的引用,而不是拷贝。如果在该线程使用返回的迭代器遍历元素的过程中,其他线程没有对list进行增删改,那么snapshot本身就是list的array,因为它们是引用关系。

    但是如果遍历期间,有其他线程对该list进行了增删改,那么snapshot就是快照了,因为增删改后list里面的数组被新数组替换了,这时候老数组只有被snapshot所引用,所以这也就说明获取迭代器后,使用改迭代器进行遍历元素时候,其它线程对该list进行的增删改是不可见的,
    因为它们操作的是两个不同的数组,这也就是弱一致性的达成。

CopyOnWriteArrayList源码阅读

标签:VID   call   dex   随机   boolean   顺序   str   快照   state   

原文地址:http://blog.51cto.com/3265857/2317288

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!