前言

ArrayList 是一个不安全的容器,在多线程调用 add 方法的时候会出现 ArrayIndexOutOfBoundsException 异常,而 Vector 虽然安全,但由于其 add 方法和 get 方法都使用了 synchronized 关键字,导致在并发时的性能令人担忧,因此,伟大的 Doug Lea 编写了 CopyOnWriteArrayList 并发容器,用于替代并发时的 ArrayList,而该类的类名叫 “写的时候拷贝集合”。也非常符合他的设计,那么,我们就看看他是如何实现的。

源码剖析

既然是 ArrayList ,第一个看的当然是 add 方法。

add 方法源码

      public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

该方法步骤如下:

  1. 使用重入锁锁住代码块。
  2. 调用 getArray 方法获取当前数组,调用 Arrays 工具类的 copyof 方法,在原来数组长度的基础上加一创建一个新数组,然后将元素添加到新数组的最后以为,这点和 ArrayList 不同, ArrayList 需要扩容的时候在原有的基础上扩容一半。
  3. 调用 setArray 方法,将新数组赋值到成员变量 array 中,注意:该变量是 volatile 的。因此,其他线程可以立即看到他。最后释放锁。

再看看 get 方法:

   public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}

可以看到该方法非常简单:获取到成员变量 array,根据下标获取。没有使用锁,完全支持并发。

从 add 方法和 get 方法中,我们看出了作者的意图,Doug Lea 认为容器 get 的操作比 add 的操作频繁,使用了类似读写分离的方式,读读操作完全并发,而写的时候,并不修改原有的内容,这对于保证当前在读线程的数据一致性非常重要,然后对原有的数据进行一次复制,将改写的内容写入到副本中,写完之后,再将修改完的副本替换原来的数据。这样就可以保证写操作不会影响读了。同时使用 volatile 变量,也保证了内存可见性,更新之后立即就能被其他线程看到。

该类保证了并发时的安全,同时,相比于 Vector 性能要高出很多(读读完全并发)。而 Vector 同时只能有一个线程进行读写,简直可怕。

但该类也不是完美的。该类的迭代器不支持 remove 操作,也不支持 set 操作,也不支持 add 操作。在迭代器中调用这 三个方法将抛出 UnsupportedOperationException 异常。

但是该类可以在 for 循环中做删除操作,这点和 ArrayList 也是不一样的,因为该类每次删除之后也都是 拷贝重写赋值。而 ArrayList 使用 for 循环删除实际上使用的迭代器的 next 方法,而迭代器每次都会检查ArrayList 的状态,调用 checkForComodification 方法,因此会抛出 ConcurrentModificationException 异常。ArrayList 必须使用迭代器的 remove 方法。

        public void remove() {
throw new UnsupportedOperationException();
} public void set(E e) {
throw new UnsupportedOperationException();
} public void add(E e) {
throw new UnsupportedOperationException();
}

总结

总的来说,并发环境下,强烈建议使用该类代替 ArrayList,该类的读读操作可保证完全并发。支持 for 循环做删除操作。不支持迭代器remove ,set, add。

并发编程之 CopyOnWriteArrayList 源码剖析的更多相关文章

  1. 并发编程之 ConcurrentLinkedQueue 源码剖析

    前言 今天我们继续分析 java 并发包的源码,今天的主角是谁呢?ConcurrentLinkedQueue,上次我们分析了并发下 ArrayList 的替代 CopyOnWriteArrayList ...

  2. 并发编程之 ThreadLocal 源码剖析

    前言 首先看看 JDK 文档的描述: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局 ...

  3. 并发编程之 LinkedBolckingQueue 源码剖析

    前言 JDK 1.5 之后,Doug Lea 大神为我们写了很多的工具,整个 concurrent 包基本都是他写的.也为我们程序员写好了很多工具,包括我们之前说的线程池,重入锁,线程协作工具,Con ...

  4. 并发编程之 AQS 源码剖析

    前言 JDK 1.5 的 java.util.concurrent.locks 包中都是锁,其中有一个抽象类 AbstractQueuedSynchronizer (抽象队列同步器),也就是 AQS, ...

  5. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

  6. 并发编程之 Condition 源码分析

    前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...

  7. 并发编程之 Semaphore 源码分析

    前言 并发 JUC 包提供了很多工具类,比如之前说的 CountDownLatch,CyclicBarrier ,今天说说这个 Semaphore--信号量,关于他的使用请查看往期文章并发编程之 线程 ...

  8. 并发编程之 CyclicBarrier 源码分析

    前言 在之前的介绍 CountDownLatch 的文章中,CountDown 可以实现多个线程协调,在所有指定线程完成后,主线程才执行任务. 但是,CountDownLatch 有个缺陷,这点 JD ...

  9. 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟

    1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...

随机推荐

  1. ubuntu 修改mysql 5.7数据库密码

    1.vi /ect/mysql/debian 查看debain-sys-maint用户的密码 2.登录mysql 4.切换到mysql数据库,更新 user 表: update user set au ...

  2. uniGUI for C++ builder之杂七杂八小知识

    uniGUI for C++ builder之杂七杂八小知识 2018年09月30日 22:16:05 中国银行之路在脚下 阅读数:41 标签: uniguibuilder 更多 个人分类: C++ ...

  3. Windows的cmd窗口显示utf8字符

    用XeLaTeX的时候,查字体需要用fc-list命令,XeLaTeX用的都是utf编码,所以fc-list输出的字体信息也是utf编码.因此需要把cmd窗口也改成utf8编码才能看到这些字体信息.U ...

  4. 在ASP.NET Core中,静态类如何读取配置文件

    这是今天下午一个同事问我的问题,如何在静态类中读取json配置文件.我当时并没有告诉他如何如何去做,办法肯定是有,但是这种编程思维确实得改改了.静态类.静态方法不是面向对象编程的最佳实践..NET C ...

  5. Google guava cache源码解析1--构建缓存器(3)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 下面介绍在LocalCache(CacheBuilder, CacheLoader)中调用的一些方法: Ca ...

  6. 【BZOJ4755】 [Jsoi2016]扭动的回文串

    BZOJ4755 [Jsoi2016]扭动的回文串 Solution 考虑对于他给出的 A中的一个回文串: B中的一个回文串: 或者某一个回文的扭动字符串S(i,j,k) 这样子几个限制,我们1,2就 ...

  7. Python3.5 学习二十二

    回顾: 发送请求时:发送请求头和请求数据 request.META和request.request.body 响应请求时:响应头和响应返回数据 response.HEADER和response.bod ...

  8. Spring Boot中使用Swagger2构建RESTful API文档

    在开发rest api的时候,为了减少与其他团队平时开发期间的频繁沟通成本,传统做法我们会创建一份RESTful API文档来记录所有接口细节,然而这样的做法有以下几个问题: 1.由于接口众多,并且细 ...

  9. Postgres 的 JSON / JSONB 类型

    从 MySQL 5.7.8 开始,MySQL 支持原生的 JSON 数据类型. 一.介绍 json是对输入的完整拷贝,使用时再去解析,所以它会保留输入的空格,重复键以及顺序等.而jsonb是解析输入后 ...

  10. [转载]windows下安装Python虚拟环境virtualenv,virtualenvwrapper-win

    1 前言 由于Python的版本众多,还有Python2和Python3的争论,因此有些软件包或第三方库就容易出现版本不兼容的问题. 通过 virtualenv 这个工具,就可以构建一系列 虚拟的Py ...