CopyOnWriteArrayList线程安全分析
CopyOnWriteArrayList是开发过程中常用的一种并发容器,多用于读多写少的并发场景。但是CopyOnWriteArrayList真的能做到完全的线程安全吗? 答案是并不能。
一、CopyOnWriteArrayList原理
我们可以看出当我们向容器添加或删除元素的时候,不直接往当前容器添加删除,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加删除元素,添加删除完元素之后,再将原容器的引用指向新的容器,整个过程加锁,保证了写的线程安全。
1 public boolean add(E e) {
2 synchronized (lock) {
3 Object[] elements = getArray();
4 int len = elements.length;
5 Object[] newElements = Arrays.copyOf(elements, len + 1);
6 newElements[len] = e;
7 setArray(newElements);
8 return true;
9 }
10 }
11
12 public E remove(int index) {
13 synchronized (lock) {
14 Object[] elements = getArray();
15 int len = elements.length;
16 E oldValue = get(elements, index);
17 int numMoved = len - index - 1;
18 if (numMoved == 0)
19 setArray(Arrays.copyOf(elements, len - 1));
20 else {
21 Object[] newElements = new Object[len - 1];
22 System.arraycopy(elements, 0, newElements, 0, index);
23 System.arraycopy(elements, index + 1, newElements, index,
24 numMoved);
25 setArray(newElements);
26 }
27 return oldValue;
28 }
29 }
而因为写操作的时候不会对当前容器做任何处理,所以我们可以对容器进行并发的读,而不需要加锁,也就是读写分离。
1 public E get(int index) {
2 return get(getArray(), index);
3 }
一般来讲我们使用时,会用一个线程向容器中添加元素,一个线程来读取元素,而读取的操作往往更加频繁。写操作加锁保证了线程安全,读写分离保证了读操作的效率,简直完美。
二、数组越界
但想象一下如果这时候有第三个线程进行删除元素操作,读线程去读取容器中最后一个元素,读之前的时候容器大小为i,当去读的时候删除线程突然删除了一个元素,这个时候容器大小变为了i-1,读线程仍然去读取第i个元素,这时候就会发生数组越界。
测试一下,首先向CopyOnWriteArrayList里面塞10000个测试数据,启动两个线程,一个不断的删除元素,一个不断的读取容器中最后一个数据。
1 public void test(){
2 for(int i = 0; i<10000; i++){
3 list.add("string" + i);
4 }
5
6 new Thread(new Runnable() {
7 @Override
8 public void run() {
9 while (true) {
10 if (list.size() > 0) {
11 String content = list.get(list.size() - 1);
12 }else {
13 break;
14 }
15 }
16 }
17 }).start();
18
19 new Thread(new Runnable() {
20 @Override
21 public void run() {
22 while (true) {
23 if(list.size() <= 0){
24 break;
25 }
26 list.remove(0);
27 try {
28 Thread.sleep(10);
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 }
33 }
34 }).start();
35 }
运行,可以看出删除到第7个元素的时候就发生了数组越界:

从上可以看出CopyOnWriteArrayList并不是完全意义上的线程安全,如果涉及到remove操作,一定要谨慎处理。
CopyOnWriteArrayList线程安全分析的更多相关文章
- CopyOnWriteArrayList 源码分析 基于jdk1.8
CopyOnWriteArrayList 源码分析: 1:成员属性: final transient ReentrantLock lock = new ReentrantLock(); //内部是 ...
- Java线程问题分析定位
Java线程问题分析定位 分析步骤: 1.使用top命令查看系统资源占用情况,发现Java进程占用大量CPU资源,PID为11572: 2.显示进程详细列表命令:ps -mp 11572 -o THR ...
- Android系统--输入系统(七)Reader_Dispatcher线程启动分析
Android系统--输入系统(七)Reader_Dispatcher线程启动分析 1. Reader/Dispatcher的引入 对于输入系统来说,将会创建两个线程: Reader线程(读取事件) ...
- Android系统--输入系统(十三)Dispatcher线程情景分析_Reader线程传递事件
Android系统--输入系统(十三)Dispatcher线程情景分析_Reader线程传递事件 1. 输入按键 我们知道Android系统的按键分为三类:(1)Global Key;(2)Syste ...
- Android系统--输入系统(十四)Dispatcher线程情景分析_dispatch前处理
Android系统--输入系统(十四)Dispatcher线程情景分析_dispatch前处理 1. 回顾 我们知道Android输入系统是Reader线程通过驱动程序得到上报的输入事件,还要经过处理 ...
- Spring中获取request的几种方法,及其线程安全性分析
前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性. 原创不易,如果觉得文章对你有帮助,欢迎点赞.评论.文章有疏漏之处,欢迎批评指正. 欢迎 ...
- 【转】java线上程序排错经验2 - 线程堆栈分析
前言 在线上的程序中,我们可能经常会碰到程序卡死或者执行很慢的情况,这时候我们希望知道是代码哪里的问题,我们或许迫切希望得到代码运行到哪里了,是哪一步很慢,是否是进入了死循环,或者是否哪一段代码有问题 ...
- [No000016E]Spring 中获取 request 的几种方法,及其线程安全性分析
前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性. 原创不易,如果觉得文章对你有帮助,欢迎点赞.评论.文章有疏漏之处,欢迎批评指正. 欢迎 ...
- java线程dump分析工具
jstack和线程dump分析 java程序性能分析之thread dump和heap dump 一.[内存dump] jmap –dump:live,format=b,file=heap.bin ...
随机推荐
- 【数量技术宅 | Python爬虫系列分享】实时监控股市重大公告的Python爬虫
实时监控股市重大公告的Python爬虫小技巧 精力有限的我们,如何更加有效率地监控信息? 很多时候特别是交易时,我们需要想办法监控一些信息,比如股市的公告.如果现有的软件没有办法实现我们的需求,那么就 ...
- 日志分析平台ELK之前端展示kibana
之前的博客一直在聊ELK集群中的存储.日志收集相关的组件的配置,但通常我们给用户使用不应该是一个黑黑的shell界面,通过接口去查询搜索:今天我们来了ELK中的前端可视化组件kibana:kibana ...
- 超级简单的照片画廊MVC
下载Gallery.zip - 23.5 MB 介绍 我想在我的个人网站上添加一个简单的图片库,但找不到任何合适的方法来从文件夹而不是数据库中挑选图片.也许我应该看得更仔细些!尽管如此,下面是我实现的 ...
- Oracle 按不同时间分组统计
1.按年 select to_char(record_date,'yyyy'), sum(col_8) as total_money from table_name where group by to ...
- C#实现迭代器
迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示.C#中使用IEnumerator接口实现,Java中使用Iterator接口实现,其中原理都差 ...
- rpm|yum安装的查看安装路径
[root@localhost src]# rpm -qa|grep grafanagrafana-7.1.0-1.x86_64[root@localhost src]# rpm -ql grafan ...
- spring boot:用swagger3生成接口文档,支持全局通用参数(swagger 3.0.0 / spring boot 2.3.2)
一,什么是swagger? 1, Swagger 是一个规范和完整的文档框架, 用于生成.描述.调用和可视化 RESTful 风格的 Web 服务文档 官方网站: https://swagger.i ...
- 跟我一起学Redis之看完这篇比常人多会三种类型实战(又搞了几个小时)
前言 对于Redis而言,很多小伙伴只关注其关键的五大基础类型:string.hash.list.set.sorted set(有序集合),其实还有三种特殊类型在很多应用场景也比较适合使用,分别是:b ...
- 函数-深入JS笔记
代码特点:高内聚,低耦合 耦合 存在执行多个相同作用代码时,这就叫耦合 if (1 > 0) { console.log('a'); } if (2 > 0) { console.log( ...
- Django( 学习第三部 Django的url路由层)
目录 url路由层 路由匹配(有名分组与无名分组) 反向解析 路由分发 名称空间(了解) Django版本区别 url 路由匹配 注意事项: url方法,第一个参数是正则表达式,只要第一个参数能够匹配 ...