线程安全的CopyOnWriteArrayList介绍
证明CopyOnWriteArrayList是线程安全的
先写一段代码证明CopyOnWriteArrayList确实是线程安全的。
ReadThread.java
import java.util.List;
public class ReadThread implements Runnable {
private List<Integer> list;
public ReadThread(List<Integer> list) {
this.list = list;
}
@Override
public void run() {
for (Integer ele : list) {
System.out.println("ReadThread:"+ele);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
WriteThread.java
import java.util.List;
public class WriteThread implements Runnable {
private List<Integer> list;
public WriteThread(List<Integer> list) {
this.list = list;
}
@Override
public void run() {
this.list.add(9);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
TestCopyOnWriteArrayList.java
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCopyOnWriteArrayList {
private void test() {
//1、初始化CopyOnWriteArrayList
List<Integer> tempList = Arrays.asList(new Integer [] {1,2});
CopyOnWriteArrayList<Integer> copyList = new CopyOnWriteArrayList<>(tempList);
//2、模拟多线程对list进行读和写
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new ReadThread(copyList));
executorService.execute(new WriteThread(copyList));
executorService.execute(new WriteThread(copyList));
executorService.execute(new WriteThread(copyList));
executorService.execute(new ReadThread(copyList));
executorService.execute(new WriteThread(copyList));
executorService.execute(new ReadThread(copyList));
executorService.execute(new WriteThread(copyList));
System.out.println("copyList size:"+copyList.size());
}
public static void main(String[] args) {
new TestCopyOnWriteArrayList().test();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
运行上面的代码,没有报出
java.util.ConcurrentModificationException
1
说明了CopyOnWriteArrayList并发多线程的环境下,仍然能很好的工作。
CopyOnWriteArrayList如何做到线程安全的
CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。
当有新元素加入的时候,如下图,创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。
当元素在新数组添加成功后,将array这个引用指向新数组。
CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。
这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。
CopyOnWriteArrayList的add操作的源代码如下:
public boolean add(E e) {
//1、先加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//2、拷贝数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//3、将元素加入到新数组中
newElements[len] = e;
//4、将array引用指向到新数组
setArray(newElements);
return true;
} finally {
//5、解锁
lock.unlock();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
由于所有的写操作都是在新数组进行的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分几种情况:
1、如果写操作未完成,那么直接读取原数组的数据;
2、如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据;
3、如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据。
可见,CopyOnWriteArrayList的读操作是可以不用加锁的。
CopyOnWriteArrayList的使用场景
通过上面的分析,CopyOnWriteArrayList 有几个缺点:
1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc
2、不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;
CopyOnWriteArrayList 合适读多写少的场景,不过这类慎用
因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。
CopyOnWriteArrayList透露的思想
如上面的分析CopyOnWriteArrayList表达的一些思想:
1、读写分离,读和写分开
2、最终一致性
3、使用另外开辟空间的思路,来解决并发冲突
参考的文章
JAVA中的COPYONWRITE容器
---------------------
作者:Sam哥哥
来源:CSDN
原文:https://blog.csdn.net/linsongbin1/article/details/54581787
版权声明:本文为博主原创文章,转载请附上博文链接!
线程安全的CopyOnWriteArrayList介绍的更多相关文章
- java并发编程(二十)----(JUC集合)CopyOnWriteArrayList介绍
这一节开始我们正式来介绍JUC集合类.我们按照List.Set.Map.Queue的顺序来进行介绍.这一节我们来看一下CopyOnWriteArrayList. CopyOnWriteArrayLis ...
- [转] 引用 Java自带的线程池ThreadPoolExecutor详细介绍说明和实例应用
PS: Spring ThreadPoolTaskExecutor vs Java Executorservice cachedthreadpool 引用 [轰隆隆] 的 Java自带的线程池Thre ...
- 线程安全的CopyOnWriteArrayList
证明CopyOnWriteArrayList是线程安全的 先写一段代码证明CopyOnWriteArrayList确实是线程安全的. ReadThread.java import java.util. ...
- Java Socket线程的设计原理介绍
转自:http://developer.51cto.com/art/201003/190001.htm Java Socket线程我们经常会用到的技术,但是有很多程序员还是有不少的使用问题,下面我们就 ...
- CopyOnWriteArrayList介绍
CopyOnWrite容器即写时复制的容器.通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后, ...
- 线程池是什么?Java四种线程池的使用介绍
使用线程池的好处有很多,比如节省系统资源的开销,节省创建和销毁线程的时间等,当我们需要处理的任务较多时,就可以使用线程池,可能还有很多用户不知道Java线程池如何使用?下面小编给大家分享Java四种线 ...
- Jmeter---不同线程组的使用介绍(转)
在添加线程组:发现线程组种类挺多的 翻查资料后对几个工具进行总结: 原本想写三个 在翻阅资料,后发现下面博文比较详情, 本文大部分来自: https://blog.csdn.net/sinat_32 ...
- java SE学习之线程同步(详细介绍)
java程序中可以允许存在多个线程,但在处理多线程问题时,必须注意这样一个问题: 当两个或多个线程同时访问同一个变量,并且一些线程需要修改这个变量时,那么这个 ...
- 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇
concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...
随机推荐
- NodeJs>------->>第二章:Node.js中交互式运行环境--------REL
第二章:Node.js中交互式运行环境--------REL 一:REPL运行环境概述 C:\Users\junliu>node > foo = 'bar' ; 'bar' > 二: ...
- python通过Cookie跳过登录验证码
1.通过浏览器获取登录cookie,找到重要的两个参数“BAIDUID”和“BDUSS”: 2.编写Selenium自动化测试脚本,跳过登录. from selenium import webdriv ...
- Laravel collection 报错 join(): Invalid arguments passed
混淆了 array 与 collection,join 并不支持 collection. array 与 collection 不同的 join 实现 collect([1, 2, 3, 4, 5]) ...
- Sony笔记本
关机的情况下按键盘 f2键.进菜单选更改 bios设置 修改 3个地方 进bios右移 boot上 第一项 ufei改成 legacy external device改成enabled 下面启动顺序改 ...
- hdu 3405 删掉某点后 求最小生成树
给出N个点的坐标 边的权值为两点间的距离 删掉其中某点 求最小生成树的权值和 要求这权值最小 因为最多50个点 所以具体是删哪个点 用枚举假如有4个点 就要求4次最小生成树 分别是2 3 4 | 1 ...
- (第6篇)大数据发展背后的强力推手——HBase分布式存储系统
摘要: 今天我们介绍可在廉价PC Server上搭建起大规模结构化存储集群的分布式存储系统——HBase. 博主福利 给大家赠送一套hadoop视频课程 授课老师是百度 hadoop 核心架构师 内容 ...
- 6-3 矩阵链成 uva 442
较为简单的栈题 思路比较好 一次ac 1.char word :word=A:直接 a[word]=xxxx,不用 a[‘word’]=xxxx #include<bits/stdc++.h& ...
- Python3中urllib使用介绍
Py2.x: Urllib库 Urllin2库 Py3.x: Urllib库 变化: 在Pytho2.x中使用import urllib2——-对应的,在Python3.x中会使用import url ...
- NumPy学习(让数据处理变简单)
NumPy学习(一) NumPy数组创建 NumPy数组属性 NumPy数学算术与算数运算 NumPy数组创建 NumPy 中定义的最重要的对象是称为 ndarray 的 N 维数组类型. 它描述相同 ...
- 动态规划-线性dp-hdu-4055
https://www.cnblogs.com/31415926535x/p/10423047.html 这道题是大连的某一年的现场赛的题hdu-4055 ,,,刚开始做线性dp的题,,看了好半天才看 ...