并发编程之 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 ...
随机推荐
- C# DataGridView中单元格Cell改变事件
DataGridView控件中的各种事件都无法直接响应Cell中内容的变化,包括KeyPress等事件,可以采用下面方法 private void dataGridViewBarcode_Editin ...
- Swift5 语言参考(二) 词法结构
词汇结构 Swift 的词法结构描述了什么样的字符序列形成了语言的有效标记.这些有效令牌构成语言的最低级构建块,用于描述后续章节中的其余语言.令牌由标识符,关键字,标点符号,文字或运算符组成. 在大多 ...
- JAVA多线程下载
package com.jan.test; import java.io.File; import java.io.IOException; import java.io.RandomAccessFi ...
- CSS3标签显示模式
HTML标签一般分为块标签和行内标签两种类型,它们也称块元素和行内元素.具体如下: 块级元素(block-level) 每个块元素通常都会独自占据一整行或多整行,可以对其设置宽度.高度.对齐等属性,常 ...
- Shell - 简明Shell入门04 - 判断语句(If)
示例脚本及注释 #!/bin/bash var=$1 # 将脚本的第一个参数赋值给变量var if test $var # test - check file types and compare va ...
- D3.js的基础部分之选择集的处理 过滤器、选择集的顺序、each()和call()的应用(v3版本)
选择集的处理 : 过滤器 有时候需要根据绑定数据对某选择集的元素进行过滤,例如某公司,只对id大于100的员工进行奖励.某学校只选拔身高超过170cm的学生等.类似这样的问题,需要根据条件获取选择集的 ...
- POJ 2876
#include<iostream> #include<string> using namespace std; ]; int main() { //freopen(" ...
- numpy中int类型与python中的int
[code] import numpy as np nparr = np.array([[1 ,2, 3, 4]]) np_int32 = nparr[0][0] # np_int=1 py_int ...
- css实现图片垂直居中
1.行高 == 高度:已知图片的高宽 <div style="text-align: center; width: 500px;height:200px; line-height:20 ...
- inotify监听文件夹的变动
inotify只能监控单层目录变化,不能监控子目录中的变化情况.如果需要监控子目录,需要在调用inotify_add_watch(int fd, char *dir, int mask):int建立监 ...