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

CopyOnWriteArrayList概述

CopyOnWriteArrayList是ArrayList的一个线程安全的变种。

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

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 GC和Full GC,也更说明了CopyOnWriteArrayList更适合于读取,而不适合与大量修改。

参考:

聊聊并发-Java中的Copy-On-Write容器

看CopyOnWriteArrayList源代码之后

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

时间: 2024-10-21 09:10:56

深入集合框架之CopyOnWriteArrayList源码剖析的相关文章

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

ArrayList概述 ArrayList底层由数组实现,非线程安全,但是数组可以动态增加,也可以叫动态数组,提供了一系列的好处,我们来深入看看: 成员变量与构造函数 /** * 存储ArrayList内的数组 */ private transient Object[] elementData; /** * The size of the ArrayList (the number of elements it contains). * ArrayList中包含了多少元素 */ private

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

为了分析ConcurrentHashMap,决定先分析一下Hashtable,两者都是线程安全的,必然会有不同的区别,Hashtable和HashMap也有很大的区别. 我们先来看看Hashtable吧. 成员变量: //存储单链表表头的数组,和HashMap中类似 private transient Entry[] table; //Hashtable中实际元素的数量 private transient int count; //Hashtable的临界值(容量* 加载因子). private

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

简介 源码分析 HashMap是JAVA抽象出来存储键值对的集合,它的底层是哈希表,有哈希表就会有冲突,所以HashMap用单链表解决冲突,也就是拉链法. HashMap是不安全的,在多线程的环境下可用ConcurrentHashMap,或者利用Collections工具类中的同步方法. 先不急于说明其他的,我们先来分析一下单链表的构造 static class Entry<K,V> implements Map.Entry<K,V> { final K key;//存放键值对的键

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

HashSet实现了Set接口,也就是说它存储的元素是无重复的. 通过源码分析我们可以发现HashSet就是HashMap的一个实例. 因为在HashMap中的键是不能重复的,我们可以把HashSet想象成HashMap中的键,而且事实也就是如此. 接下来我们具体分析一个构造函数: 版权声明:本文为博主原创文章,未经博主允许不得转载.

Java类集框架之ArrayList源码剖析

ArrayList 基于数组实现,本质上是对象引用的一个变长数组,能够动态的增加或减小其大小. 不是线程安全的,只能用在单线程环境下.多线程环境下可以考虑用Collection.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的 CopyOnWriteArrayList类 下面直接贴ArrayList的Java实现,来源JDK1.8.0_25/src.zip. public class ArrayList<E>

Java类集框架之LinkedList源码剖析

LinkedList LinkedList基于双向循环链表实现.也可以被当做堆栈,队列或双端队列进行操作.非线程安全.下面直接贴ArrayList的Java实现(只贴了部分代码),来源JDK1.8.0_25/src.zip. /** * ****双向链表对应的数据结构********* * 包含:节点值item * 前驱 pre * 后继next * @param <E> */ private static class Node<E> { E item; Node<E>

【java集合框架源码剖析系列】java源码剖析之TreeMap

注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于ArrayList的知识. 一TreeMap的定义: public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable 可以看到TreeMap是继承自AbstractMap同时实现了NavigableMap,

《python源码剖析》笔记 Python虚拟机框架

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1. Python虚拟机会从编译得到的PyCodeObject对象中依次读入每一条字节码指令, 并在当前的上下文环境中执行这条字节码指令. Python虚拟机实际上是在模拟操作中执行文件的过程 PyCodeObject对象中包含了字节码指令以及程序的所有静态信息,但没有包含 程序运行时的动态信息--执行环境(PyFrameObject) 2.Python源码中的PyFrameObject

【Java集合源码剖析】ArrayList源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/35568011 ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的C