证明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介绍的更多相关文章

  1. java并发编程(二十)----(JUC集合)CopyOnWriteArrayList介绍

    这一节开始我们正式来介绍JUC集合类.我们按照List.Set.Map.Queue的顺序来进行介绍.这一节我们来看一下CopyOnWriteArrayList. CopyOnWriteArrayLis ...

  2. [转] 引用 Java自带的线程池ThreadPoolExecutor详细介绍说明和实例应用

    PS: Spring ThreadPoolTaskExecutor vs Java Executorservice cachedthreadpool 引用 [轰隆隆] 的 Java自带的线程池Thre ...

  3. 线程安全的CopyOnWriteArrayList

    证明CopyOnWriteArrayList是线程安全的 先写一段代码证明CopyOnWriteArrayList确实是线程安全的. ReadThread.java import java.util. ...

  4. Java Socket线程的设计原理介绍

    转自:http://developer.51cto.com/art/201003/190001.htm Java Socket线程我们经常会用到的技术,但是有很多程序员还是有不少的使用问题,下面我们就 ...

  5. CopyOnWriteArrayList介绍

    CopyOnWrite容器即写时复制的容器.通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后, ...

  6. 线程池是什么?Java四种线程池的使用介绍

    使用线程池的好处有很多,比如节省系统资源的开销,节省创建和销毁线程的时间等,当我们需要处理的任务较多时,就可以使用线程池,可能还有很多用户不知道Java线程池如何使用?下面小编给大家分享Java四种线 ...

  7. Jmeter---不同线程组的使用介绍(转)

    在添加线程组:发现线程组种类挺多的  翻查资料后对几个工具进行总结: 原本想写三个 在翻阅资料,后发现下面博文比较详情, 本文大部分来自: https://blog.csdn.net/sinat_32 ...

  8. java SE学习之线程同步(详细介绍)

           java程序中可以允许存在多个线程,但在处理多线程问题时,必须注意这样一个问题:               当两个或多个线程同时访问同一个变量,并且一些线程需要修改这个变量时,那么这个 ...

  9. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

随机推荐

  1. 总结Selenium WebDriver中一些鼠标和键盘事件的使用

    在使用 Selenium WebDriver 做自动化测试的时候,会经常模拟鼠标和键盘的一些行为.比如使用鼠标单击.双击.右击.拖拽等动作:或者键盘输入.快捷键使用.组合键使用等模拟键盘的操作.在 W ...

  2. log4j2 使用详解

    转载自 Blog of 天外的星星: http://www.cnblogs.com/leo-lsw/p/log4j2tutorial.html Log4j 2的好处就不和大家说了,如果你搜了2,说明你 ...

  3. strchr()的用法

    strchr()主要有2个最有用的用法: 第一个:搜索字符串在另一字符串中的第一次出现.并返回剩余的部分 $str = "hello_chrdai_1993"; $not_incl ...

  4. SpringMVC的初始

    1:其实一开始对SSH和SSM并不是很熟悉的,对SSH可能熟悉些(Struts,Spring,Hibernate)这三个框架.但是由于框架的更新,和出现了更好的框架,一些老框架就被淘汰了,但是呢,一些 ...

  5. [转] Java中public,private,final,static等概念的解读

    作为刚入门Java的小白,对于public,private,final,static等概念总是搞不清楚,到底都代表着什么,这里做一个简单的梳理,和大家分享,若有错误请指正,谢谢~ 访问权限修饰符 pu ...

  6. POJ 2377 Bad Cowtractors (Kruskal)

    题意:给出一个图,求出其中的最大生成树= =如果无法产生树,输出-1. 思路:将边权降序再Kruskal,再检查一下是否只有一棵树即可,即根节点只有一个 #include <cstdio> ...

  7. BZOJ3064 Tyvj 1518 CPU监控 线段树

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ3064 题意概括 一个序列,要你支持以下操作: 1. 区间询问最大值 2. 区间询问历史最大值 3. ...

  8. python界面Tkinter编程(tkMessageBox对话框使用)

    python界面Tkinter编程(tkMessageBox对话框使用)     转载 https://blog.csdn.net/m_buddy/article/details/80105154 1 ...

  9. OSX 10.13 以后实现终端FTP命令(转)

    macOS10.13 以后就没了........ 这样操作亲测可以恢复 1,brew install telnet 2,brew install inetutils 3,brew link --ove ...

  10. 001.Open-Falcon简介

    一 Open-Falcon简介 监控系统是整个运维环节,乃至整个产品生命周期中最重要的一环,事前及时预警发现故障,事后提供翔实的数据用于追查定位问题.监控系统作为一个成熟的运维产品,相对成熟的解决方案 ...