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

深入集合框架之CopyOnWriteArrayList源码剖析

时间:2015-08-11 18:50:01      阅读:194      评论:0      收藏:0      [点我收藏+]

标签:java   copyonwritearraylist   源码   集合   

CopyOnWriteArrayList概述

CopyOnWriteArrayListArrayList的一个线程安全的变种。

CopyOnWriteArrayListArrayList不同处就在于是否会拷贝数组和加锁。

CopyOnWriteArrayList顾名思义就是写时复制的ArrayList,其意思就是在修改容器的元素时,并不是直接在原数组上修改,而是先拷贝了一份数组,然后在拷贝的数组上进行修改,修改完后将其引用赋值给原数组的引用。这样体现了读写分离,这样无论在任何时候我们都可以对容器进行读取。


成员变量

这里就几个常见特性解析一下:

 transient final ReentrantLock lock = new ReentrantLock();
//这便是CopyOnWriteArrayList中的锁属性,确保了在更新原数组时只有一个线程可以进来。

private volatile transient Object[] array;

这个便是原数组,我们发现与ArrayList不同的地方在于,增加了volatile属性。

这有什么用处呢?

Volaile相当于弱一级的synchronized,提供了可见性,在多线程执行环境中对每个线程都是可见的,这也就保证了所有线程拿到的数据都是一致的。

锁提供了两种级别的机制:互斥与可见

互斥是说:一个线程在修改的一个数据的时候,另一个线程不能访问这个数据。

而可见是说:一个线程在修改一个数据的时候,另一个线程拿到的值都是一致的。也就是说所有的线程都可以自动获取到volatile的最新值。

所以我们可以说synchronized在保证array写的时候的原子性,而volatile保证读array的可见性。

----------------------------------------------------------------------------------------------------------------------------

     //返回数组
    final Object[] getArray() {
        return array;
   }
      
     //数组复制
    final void setArray(Object[] a) {
        array = a;
    }


读取: 

 /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return (E)(getArray()[index]);
    }

读取的时候不会加锁,也不会拷贝数组。


写入:

    //修改数组上的某个下标的值为指定元素
    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();
	}
   }
   
   
   
     //添加元素
    public boolean add(E e) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
	    Object[] elements = getArray();
	    int len = elements.length;
  <span style="white-space:pre">	</span>//拷贝一个比原数组长度大一个单元的数组
	    Object[] newElements = Arrays.copyOf(elements, len + 1);
  <span style="white-space:pre">	</span>//在末尾添加指定的元素
	    newElements[len] = e;
  <span style="white-space:pre">	</span>//设置引用
	    setArray(newElements);
	    return true;
	} finally {
	    lock.unlock();
	}
    }
   
   

     //移除指定下标的元素
    public E remove(int index) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
	    Object[] elements = getArray();
	    int len = elements.length;
  <span style="white-space:pre">		</span>//保存旧值
	    Object oldValue = elements[index];
	    int numMoved = len - index - 1;
	    if (numMoved == 0)
  <span style="white-space:pre">		</span>//移除末尾的值
		setArray(Arrays.copyOf(elements, len - 1));
	    else {
 <span style="white-space:pre">		</span> //创建新数组
		Object[] newElements = new Object[len - 1];
  <span style="white-space:pre">		</span>//从0开始拷贝index个值
		System.arraycopy(elements, 0, newElements, 0, index);
 <span style="white-space:pre">		</span> //前移
		System.arraycopy(elements, index + 1, newElements, index,
				 numMoved);
 <span style="white-space:pre">		</span> //设置引用
		setArray(newElements);
	    }
	    return (E)oldValue;
	} finally {
	    lock.unlock();
	}
       }

从上面的三个更新、添加、删除的方法来看,我们发现CopyOnWriteArrayList在修改原数组的过程中比ArrayList多做了2件事:

1、加锁:保证我在修改数组的时候,其他人不能修改。

2、拷贝数组:无论是哪个方法,发现都需要拷贝数组。

上面的两件事就确保了CopyOnWriteArrayList在多线程的环境下可以应对自如。

----------------------------------------------------------------------------------------------------------------------------------------------------

迭代器:


CopyOnWriteArrayList的迭代器并不是快速失败的,也就是说并不会抛出ConcurrentModificationException异常。这是因为他在修改的时候,是针对与拷贝数组而言的,对于原数组没有任何影响。我们可以看出迭代器里面没有锁机制,所以只提供读取,而不支持添加修改和删除(抛出UnsupportedOperationExcetion)。

public ListIterator<E> listIterator() {
        return new COWIterator<E>(getArray(), 0);
   }
private static class COWIterator<E> implements ListIterator<E> {
        //原数组的快照
        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 boolean hasPrevious() {
            return cursor > 0;
        }

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

        public E previous() {
	    if (! hasPrevious())
                throw new NoSuchElementException();
	    return (E) snapshot[--cursor];
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor-1;
        }

        //不支持删除
        public void remove() {
            throw new UnsupportedOperationException();
        }

  //不支持修改
        public void set(E e) {
            throw new UnsupportedOperationException();
        }
      
  //不支持添加
        public void add(E e) {
            throw new UnsupportedOperationException();
        }
       }

CopyOnWriteArrayList的不足:

数据一致性问题:如果一个线程正在修改,而另一个线程此刻便读取的是旧数据,也就是说不能保证数据的一致性。

内存问题:

在添加、修改、删除的操作中,会大量的复制数组,当内存很小时,会在内存中产生许多新数组,从而提前触发一系列的young GCFull GC,也更说明了CopyOnWriteArrayList更适合于读取,而不适合与大量修改。



参考:


版权声明:本文为博主原创文章,转载请注明出处。

深入集合框架之CopyOnWriteArrayList源码剖析

标签:java   copyonwritearraylist   源码   集合   

原文地址:http://blog.csdn.net/u014307117/article/details/47423373

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