java.util.concurrent在相应的并发集合的包中定义的通用集合类,为了有效地处理并发场景。间CopyOnWriteArrayList它是合适ArrayList。顾名思义CopyOnWrite,当写副本,在这里写下包含集合改变操作,将创建一个副本。

CopyOnWriteArrayList的实现

类的定义

  1. public class CopyOnWriteArrayList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable

能够看到没有继承不论什么子类,实现接口和ArrayList类似。

关键属性

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

相同是採用数组方式实现,多了一个volatile声明,用于保证线程可见性。没有size声明表示实际包括元素的大小。多了一个ReentrantLock对象声明。

常见方法

构造方法

  1. public CopyOnWriteArrayList() {
  2. setArray(new Object[0]); //默认创建一个空数组
  3. }
  4. public CopyOnWriteArrayList(Collection<? extends E> c) {
  5. Object[] elements = c.toArray();
  6. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  7. if (elements.getClass() != Object[].class)
  8. elements = Arrays.copyOf(elements, elements.length, Object[].class);//拷贝一份数组
  9. setArray(elements);
  10. }

size方法,直接返回数组大小,说明array数组仅仅包括实际大小的空间

  1. public int size() {
  2. return getArray().length;
  3. }

get方法,和ArrayList中类似,只是没有index的范围推断

  1. public E get(int index) {
  2. return (E)(getArray()[index]);
  3. }

add方法,能够看到不管是在尾部还是指定位置加入。都有锁定和解锁操作。在设置值之前都先将原先数组拷贝一份并扩容至size+1大小。

  1. public boolean add(E e) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock(); //锁住
  4. try {
  5. Object[] elements = getArray();
  6. int len = elements.length;
  7. Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝array属性,并扩展为length+1大小
  8. newElements[len] = e;
  9. setArray(newElements);
  10. return true;
  11. } finally {
  12. lock.unlock(); //解锁
  13. }
  14. }
  15.  
  16. public void add(int index, E element) {
  17. final ReentrantLock lock = this.lock;
  18. lock.lock();
  19. try {
  20. Object[] elements = getArray();
  21. int len = elements.length;
  22. if (index > len || index < 0)
  23. throw new IndexOutOfBoundsException("Index: "+index+
  24. ", Size: "+len);
  25. Object[] newElements;
  26. int numMoved = len - index;
  27. if (numMoved == 0) //尾部加入
  28. newElements = Arrays.copyOf(elements, len + 1);
  29. else {
  30. newElements = new Object[len + 1];
  31. //elements[0,index) ---> newElements[0,index)
  32. System.arraycopy(elements, 0, newElements, 0, index);
  33. //elements[index,len) --> newElements[index+1,len+1)
  34. System.arraycopy(elements, index, newElements, index + 1,
  35. numMoved);
  36. }
  37. newElements[index] = element;
  38. setArray(newElements);
  39. } finally {
  40. lock.unlock();
  41. }
  42. }

set方法,ArrayList中set方法直接改变数组中相应的引用,这里须要拷贝数组然后再设置。

(else那个分支没看懂,为什么值没有改变还须要设置来保证volatile写语义)

  1. public E set(int index, E element) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. Object[] elements = getArray();
  6. Object oldValue = elements[index];
  7. if (oldValue != element) {
  8. int len = elements.length;
  9. Object[] newElements = Arrays.copyOf(elements, len);
  10. newElements[index] = element;
  11. setArray(newElements);
  12. } else {
  13. // Not quite a no-op; ensures volatile write semantics
  14. setArray(elements);
  15. }
  16. return (E)oldValue;
  17. } finally {
  18. lock.unlock();
  19. }
  20. }

remove(int)方法,和指定位置加入类似,须要拷贝[0,index)和[index+1,len)之间的元素

  1. public E remove(int index) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. Object[] elements = getArray();
  6. int len = elements.length;
  7. Object oldValue = elements[index];
  8. int numMoved = len - index - 1;nt
  9. if (numMoved == 0) //删除最后一个元素
  10. setArray(Arrays.copyOf(elements, len - 1));
  11. else {
  12. Object[] newElements = new Object[len - 1];
  13. //elements[0,index) --> newElements[0,index)
  14. System.arraycopy(elements, 0, newElements, 0, index);
  15. //elements[index+1,len) --> newElements[index,len-1)
  16. System.arraycopy(elements, index + 1, newElements, index,
  17. numMoved);
  18. setArray(newElements);
  19. }
  20. return (E)oldValue;
  21. } finally {
  22. lock.unlock();
  23. }
  24. }

remove(Object)方法,分配一个len-1大小的新数组。遍历原来数组,假设找到则将原来数组以后的元素复制到新数组中并将list设置为新数组,否则直接给新数组赋值上原来数组。

  1. public boolean remove(Object o) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. Object[] elements = getArray();
  6. int len = elements.length;
  7. if (len != 0) {
  8. // Copy while searching for element to remove
  9. // This wins in the normal case of element being present
  10. int newlen = len - 1;
  11. Object[] newElements = new Object[newlen];
  12.  
  13. for (int i = 0; i < newlen; ++i) {
  14. if (eq(o, elements[i])) {
  15. // found one; copy remaining and exit
  16. for (int k = i + 1; k < len; ++k)
  17. newElements[k-1] = elements[k];
  18. setArray(newElements);
  19. return true;
  20. } else
  21. newElements[i] = elements[i];
  22. }
  23.  
  24. // special handling for last cell
  25. if (eq(o, elements[newlen])) {
  26. setArray(newElements);
  27. return true;
  28. }
  29. }
  30. return false;
  31. } finally {
  32. lock.unlock();
  33. }
  34. }

迭代器的实现

ArrayList中迭代器支持fast fail,一旦检測到遍历过程中发送了改动则会抛出ConcurrentModificationException;CopyOnWriteArrayList的迭代器因为改动的时候都会又一次copy一份数组,因此不存在并发改动问题。也不会抛出ConcurrentModificationException。

相同支持单向和双向迭代器,其iterator和listIterator方法都是通过内部类COWIterator创建。仅仅是前者返回接口限定为单向迭代Iterator<E>。

COWIterator定义

  1. /** Snapshot of the array **/
  2. private final Object[] snapshot;
  3. /** Index of element to be returned by subsequent call to next. */
  4. private int cursor;

构造器

  1. private COWIterator(Object[] elements, int initialCursor) {
  2. cursor = initialCursor;
  3. snapshot = elements;
  4. }

iterator和listIterator中会传递当前数组的引用和cursor(无參方法为0,有參数方法为相应值)

常见方法

  1. public boolean hasNext() {
  2. return cursor < snapshot.length;
  3. }
  4. public boolean hasPrevious() {
  5. return cursor > 0;
  6. }
  7. public E next() {
  8. if (! hasNext())
  9. throw new NoSuchElementException();
  10. return (E) snapshot[cursor++];
  11. }
  12. public E previous() {
  13. if (! hasPrevious())
  14. throw new NoSuchElementException();
  15. return (E) snapshot[--cursor];
  16. }

另外其它add、remove和set改动容器的方法都没有实现,直接throw new UnsupportedOperationException();

总结

1. CopyOnWriteArrayList的迭代器保留一个运行底层基础数组的引用,这个数组当前位于迭代器的起始位置,因为基础数组不会被改动(改动都是复制一个新的数组),因此对其同步仅仅须要保证数组内容的可见性。多个线程能够同一时候对这个容器进行迭代。而不会彼此干扰或者与改动容器的线程互相干扰。

不会抛出CocurrentModificationException。而且返回元素与创建迭代器创建时的元素全然一致。不必考虑之后改动操作带来影响。

2. 每次改动容器都会复制底层数组,这须要一定开销,特别是容器规模较大。仅当迭代操作远远多于改动操作时,才应该使用CopyOnWriteArrayList。

版权声明:本文博主原创文章。博客,未经同意不得转载。

CopyOnWriteArrayList源代码阅读器的更多相关文章

  1. spring framework 4 源代码阅读器(1) --- 事前准备

    在你开始看代码.的第一件事要做的就是下载代码. 这里:https://github.com/spring-projects/spring-framework 下载完整的使用发现gradle建立管理工具 ...

  2. EnumMap源代码阅读器

    EnumMap是一个用于存放键值为enum类型的map.全部的键值必须来自一个单一的enum类型.EnumMap内部用数组表示效率更高. EnumMap维持键值的自然顺序(即枚举类型常量声明的顺序), ...

  3. Silverlight类百度文库在线文档阅读器

    百度文库阅读器是基于Flash的,用Silverlight其实也可以做. 我实现的在线阅读器可以应用于内网文档发布,在线阅览审批等.没有过多的堆积功能,专注于核心功能.主要有以下特性: 1. 基于XP ...

  4. 【转】Tomcat总体结构(Tomcat源代码阅读系列之二)

    本文是Tomcat源代码阅读系列的第二篇文章,我们在本系列的第一篇文章:在IntelliJ IDEA 和 Eclipse运行tomcat 7源代码一文中介绍了如何在intelliJ IDEA 和 Ec ...

  5. 淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式

    淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式 什么是Database,什么是Schema,什么是Table,什么是列,什么是行,什么是User?我们能够能够把Data ...

  6. Web版RSS阅读器(二)——使用dTree树形加载rss订阅分组列表

    在上一边博客<Web版RSS阅读器(一)——dom4j读取xml(opml)文件>中已经讲过如何读取rss订阅文件了.这次就把订阅的文件读取到页面上,使用树形结构进行加载显示. 不打算使用 ...

  7. Silverlight类百度文库在线文档阅读器(转)

    百度文库阅读器是基于Flash的,用Silverlight其实也可以做. 我实现的在线阅读器可以应用于内网文档发布,在线阅览审批等.没有过多的堆积功能,专注于核心功能.主要有以下特性: 1. 基于XP ...

  8. 【转】Tomcat源代码阅读系列

    在IntelliJ IDEA 和 Eclipse运行tomcat 7源代码(Tomcat源代码阅读系列之一) Tomcat总体结构(Tomcat源代码阅读系列之二) Tomcat启动过程(Tomcat ...

  9. Adobe阅读器漏洞(adobe_cooltype_sing)学习研究

    实验环境:Kali 2.0+Windows XP sp3+Adobe Reader 9.0.0 类别:缓冲区溢出 描述:这个漏洞针对Adobe阅读器9.3.4之前的版本,一个名为SING表对象中一个名 ...

随机推荐

  1. DL动态载入框架技术文档

    DL动态载入框架技术文档 DL技术交流群:215680213 1. Android apk动态载入机制的研究 2. Android apk动态载入机制的研究(二):资源载入和activity生命周期管 ...

  2. 利用linux BT5来破解无线 破解无线

    下面是自己整理的详细步骤,版权小冯全部. 一.提前准备好bt5的ISO镜像文件.和虚拟机,提前把虚拟机安装好.然后进行安装bt5. 二.进入页面,点击statx.进入可视化界面. 三.进入主界面后.下 ...

  3. spring MVC 下载文件(转)

    springle MVC中如何下载文件呢? 比struts2 下载文件简单得多 先看例子: @ResponseBody @RequestMapping(value = "/download& ...

  4. CC 3-Palindromes(manacher)

    传送门:3-Palindromes 题意:求为回文串且能整除3且不前导0的子串个数. 分析:由 manacher算法O(N)可算出以i为坐标的最长为p[i]回文子串,且Si-k,Si-k+1..... ...

  5. Hasor:生命周期

    首先引用Wiki的介绍一下Hasor:     “Hasor是一款开源框架.它是为了解决企业模块化开发中复杂性而创建的.Hasor遵循简单的依赖.单一职责,在开发多模块企业项目中更加有调理.然而Has ...

  6. HDU 3217 Health(状压DP)

    Problem Description Unfortunately YY gets ill, but he does not want to go to hospital. His girlfrien ...

  7. 【mysql】关于子查询的一个例子

    假设表my_tbl包含三个字段a,b,c:现在需要查询表中列a的每个不同值下的列b为最小值的记录量. 比如表记录为: a  b  c 1  3  'cd' 2  3  'nhd' 1  5  'bg' ...

  8. BGP的状态机制

    Idle 状态:即空闲状态,不接受任何BGP的连接,等待Start事件的产生,如果有start事件产生,若有start事件产生,系统开启ConnectRetry定时器,向邻居发起TCP连接,并将状态变 ...

  9. poj2486(树形dp)

    题目链接:http://poj.org/problem?id=2486 题意:一颗树,n个点(1-n),n-1条边,每个点上有一个权值,求从1出发,走m步,最多能遍历到的权值. 分析:非常不错的树形d ...

  10. win7系统u盘安装过程

    1.准备好带有启动项的U盘,并把镜像解压到里面去 2.插上u盘,开机长按del键进入bois设置界面 在boot页面 1.boot device priority->1st boot devic ...