并发编程之 CopyOnWriteArrayList 源码剖析

前言
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();
}
}
该方法步骤如下:
- 使用重入锁锁住代码块。
- 调用 getArray 方法获取当前数组,调用 Arrays 工具类的 copyof 方法,在原来数组长度的基础上加一创建一个新数组,然后将元素添加到新数组的最后以为,这点和 ArrayList 不同, ArrayList 需要扩容的时候在原有的基础上扩容一半。
- 调用 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 源码剖析的更多相关文章
- 并发编程之 ConcurrentLinkedQueue 源码剖析
前言 今天我们继续分析 java 并发包的源码,今天的主角是谁呢?ConcurrentLinkedQueue,上次我们分析了并发下 ArrayList 的替代 CopyOnWriteArrayList ...
- 并发编程之 ThreadLocal 源码剖析
前言 首先看看 JDK 文档的描述: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局 ...
- 并发编程之 LinkedBolckingQueue 源码剖析
前言 JDK 1.5 之后,Doug Lea 大神为我们写了很多的工具,整个 concurrent 包基本都是他写的.也为我们程序员写好了很多工具,包括我们之前说的线程池,重入锁,线程协作工具,Con ...
- 并发编程之 AQS 源码剖析
前言 JDK 1.5 的 java.util.concurrent.locks 包中都是锁,其中有一个抽象类 AbstractQueuedSynchronizer (抽象队列同步器),也就是 AQS, ...
- 并发编程之 Exchanger 源码分析
前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...
- 并发编程之 Condition 源码分析
前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...
- 并发编程之 Semaphore 源码分析
前言 并发 JUC 包提供了很多工具类,比如之前说的 CountDownLatch,CyclicBarrier ,今天说说这个 Semaphore--信号量,关于他的使用请查看往期文章并发编程之 线程 ...
- 并发编程之 CyclicBarrier 源码分析
前言 在之前的介绍 CountDownLatch 的文章中,CountDown 可以实现多个线程协调,在所有指定线程完成后,主线程才执行任务. 但是,CountDownLatch 有个缺陷,这点 JD ...
- 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟
1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...
随机推荐
- linux命令 find的应用
1.列出当前目录及子目录下所有文件和文件夹 2.在 /home目录下查找以 “.txt”结尾的文件名 3.在当前目录下查找所有以“.txt”结尾的文件 4基于目录深度搜索(向下最大深度限制为3) 5. ...
- 7.代理handler
简单的自定义opener() import urllib.request #构建一个HTTPHandler处理器对象,支持处理HTTP请求 http_handler=urllib.request.HT ...
- CentOS使用vsftpd开启FTP服务以及配置用户
1.安装服务 #yum install vsftpd 2.配置 #vi /etc/vsftpd/vsftpd.conf # 禁止匿名访问 anonymous_enable=NO # 允许本地用户登录F ...
- OpencvSharp 在WPF的Image控件中显示图像
1.安装OpencvSharp 我使用的是VS2013 社区版,安装OpencvSharp3.0 在线安装方法:进入Tools,打开NuGet的包管理器 搜索Opencv 安装之后就可以使用,无需再做 ...
- Windows核心编程:第14章 探索虚拟内存
Github https://github.com/gongluck/Windows-Core-Program.git //第14章 探索虚拟内存.cpp: 定义应用程序的入口点. // #inclu ...
- UWP Button添加圆角阴影(一)
原文:UWP Button添加圆角阴影(一) 众所周知,17763之前的UWP控件,大部分是没有圆角属性的:而阴影也只有17763中的ThemeShadow可以直接在xaml中使用,之前的版本只能用D ...
- 【转】C#模拟http 发送post或get请求
原文地址:http://www.cnblogs.com/xssxss/archive/2012/07/03/2574554.html 模拟POST Json public static string ...
- Linux Shell常用脚本整理
轮询检测Apache状态并启用钉钉报警◆ #!/bin/bash shell_user="root" shell_domain="apache" shell_l ...
- Django(ORM查询1)
day69 参考:http://www.cnblogs.com/liwenzhou/p/8660826.html 在Python脚本中调用Django环境 orm1.py import os if _ ...
- C#6.0语言规范(十一) 结构
结构与类类似,因为它们表示可以包含数据成员和函数成员的数据结构.但是,与类不同,结构是值类型,不需要堆分配.结构类型的变量直接包含结构的数据,而类类型的变量包含对数据的引用,后者称为对象. 结构对于具 ...