java.util.concurrent.CopyOnWriteArrayList CopyOnWriteArraySet

一、简介

    JDK5中添加了新的concurrent包,其中包含了很多并发容器,这些容器针对多线程环境进行了优化,大大提高了容器类在并发环境下的执行效率。

    CopyOnWriteArrayList类是一个线程安全的List接口的实现,在该类的内部进行元素的写操作时,底层的数组将被完整的复制,这对于读操作远远多于写操作的应用非常适合。在CopyOnWriteArrayList上进行操作时,读操作不需要加锁,而写操作类实现中对其进行了加锁。

二、具体实现

    CopyOnWriteArrayList底层的定义如下:

 

  1. public class CopyOnWriteArrayList<E>     
  2.         implements List<E>, RandomAccess, Cloneable, java.io.Serializable {     
  3.     
  4.     private volatile transient E[] array;     
  5.     
  6.     private E[] array() { return array; }     
  7.     
  8.     // 该操作是加锁的,防止array在copy的时候被替换      
  9.     private synchronized void copyIn(E[] toCopyIn, int first, int n) {     
  10.         array  = (E[]) new Object[n];     
  11.         System.arraycopy(toCopyIn, first, array, 0, n);     
  12.     }     
  13.     
  14.   ...     
  15. }  


读写操作:

  1. public class CopyOnWriteArrayList<E>     
  2.         implements List<E>, RandomAccess, Cloneable, java.io.Serializable {     
  3.     
  4.     public E get(int index) {     
  5.      // 由于包括rangeCheck和index两个操作,并不是直接在array上执行     
  6.    // 而是使用本地变量elementData引用array数组,防止两个操作之间     
  7.    // array被替换     
  8.      E[] elementData = array();     
  9.         rangeCheck(index, elementData.length);     
  10.         return elementData[index];     
  11.     }     
  12.          
  13.     public synchronized E set(int index, E element) { // 是同步的     
  14.         int len = array.length;     
  15.         rangeCheck(index, len);     
  16.         E oldValue = array[index];     
  17.     
  18.         // 判断该写的元素与原数据是否相同     
  19.         boolean same = (oldValue == element ||     
  20.         (element != null && element.equals(oldValue)));     
  21.              
  22.         if (!same) {     
  23.             // [1] 创建一个新数组,将原array的值拷贝至新数组     
  24.         E[] newArray = (E[]) new Object[len];     
  25.             System.arraycopy(array, 0, newArray, 0, len);     
  26.             // [2] set的元素     
  27.         newArray[index] = element;     
  28.             // [3] 替换底层array数组     
  29.         array = newArray;     
  30.         }     
  31.         return oldValue;     
  32.     }     
  33.     
  34.   ...     
  35. }    


add和remove也采用相同的技术:

  1. public class CopyOnWriteArrayList<E>     
  2.         implements List<E>, RandomAccess, Cloneable, java.io.Serializable {     
  3.     
  4.     public synchronized boolean add(E element) {     
  5.         // [1] new and copy     
  6.         int len = array.length;     
  7.         E[] newArray = (E[]) new Object[len+1];     
  8.         System.arraycopy(array, 0, newArray, 0, len);     
  9.         // [2] add element     
  10.         newArray[len] = element;     
  11.         // [3] change base array     
  12.         array = newArray;     
  13.         return true;     
  14.     }     
  15.     
  16.     public synchronized E remove(int index) {     
  17.         int len = array.length;     
  18.         rangeCheck(index, len);     
  19.         E oldValue = array[index];     
  20.         // new一个新的数组     
  21.       E[] newArray = (E[]) new Object[len-1];     
  22.         // copy index之前的元素     
  23.       System.arraycopy(array, 0, newArray, 0, index);     
  24.         // copy余下的元素     
  25.       int numMoved = len - index - 1;     
  26.         if (numMoved > 0)     
  27.             System.arraycopy(array, index+1, newArray, index, numMoved);     
  28.         // 替换array引用         
  29.      array = newArray;     
  30.         return oldValue;     
  31.     }     
  32.     
  33.   ...     
  34. }    


特别注意:在CopyOnWriteArrayList上获得的Iterator是不能进行set和remove操作的,否则会抛出异常。

 

除了加锁外,其实还有一种方式可以防止并发修改异常,这就是将读写分离技术(不是数据库上的)。

先回顾一下一个常识:

1、JAVA中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,而且速度非常快,几乎不需要CPU时间。

2、JAVA中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。

 

基于上面这个常识,我们再来探讨下面这个问题:

在CopyOnWriteArrayList里处理写操作(包括add、remove、set等)是先将原始的数据通过JDK1.6的Arrays.copyof()来生成一份新的数组

然后在新的数据对象上进行写,写完后再将原来的引用指向到当前这个数据对象(这里应用了常识1),这样保证了每次写都是在新的对象上(因为要保证写的一致性,这里要对各种写操作要加一把锁,JDK1.6在这里用了重入锁),

然后读的时候就是在引用的当前对象上进行读(包括get,iterator等),不存在加锁和阻塞,针对iterator使用了一个叫COWIterator的阉割版迭代器,因为不支持写操作,当获取CopyOnWriteArrayList的迭代器时,是将迭代器里的数据引用指向当前引用指向的数据对象,无论未来发生什么写操作,都不会再更改迭代器里的数据对象引用,所以迭代器也很安全(这里应用了常识2)。

CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差,但是读操作因为操作的对象和写操作不是同一个对象,读之间也不需要加锁,读和写之间的同步处理只是在写完后通过一个简单的“=”将引用指向新的数组对象上来,这个几乎不需要时间,这样读操作就很快很安全,适合在多线程里使用,绝对不会发生ConcurrentModificationException,所以最后得出结论:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。

 

CopyOnWriteArraySet 基本原理同上,其内部含有:

private final CopyOnWriteArrayList<E> al;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值