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线程安全分析的更多相关文章

  1. CopyOnWriteArrayList 源码分析 基于jdk1.8

    CopyOnWriteArrayList  源码分析: 1:成员属性: final transient ReentrantLock lock = new ReentrantLock();  //内部是 ...

  2. Java线程问题分析定位

    Java线程问题分析定位 分析步骤: 1.使用top命令查看系统资源占用情况,发现Java进程占用大量CPU资源,PID为11572: 2.显示进程详细列表命令:ps -mp 11572 -o THR ...

  3. Android系统--输入系统(七)Reader_Dispatcher线程启动分析

    Android系统--输入系统(七)Reader_Dispatcher线程启动分析 1. Reader/Dispatcher的引入 对于输入系统来说,将会创建两个线程: Reader线程(读取事件) ...

  4. Android系统--输入系统(十三)Dispatcher线程情景分析_Reader线程传递事件

    Android系统--输入系统(十三)Dispatcher线程情景分析_Reader线程传递事件 1. 输入按键 我们知道Android系统的按键分为三类:(1)Global Key;(2)Syste ...

  5. Android系统--输入系统(十四)Dispatcher线程情景分析_dispatch前处理

    Android系统--输入系统(十四)Dispatcher线程情景分析_dispatch前处理 1. 回顾 我们知道Android输入系统是Reader线程通过驱动程序得到上报的输入事件,还要经过处理 ...

  6. Spring中获取request的几种方法,及其线程安全性分析

    前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性. 原创不易,如果觉得文章对你有帮助,欢迎点赞.评论.文章有疏漏之处,欢迎批评指正. 欢迎 ...

  7. 【转】java线上程序排错经验2 - 线程堆栈分析

    前言 在线上的程序中,我们可能经常会碰到程序卡死或者执行很慢的情况,这时候我们希望知道是代码哪里的问题,我们或许迫切希望得到代码运行到哪里了,是哪一步很慢,是否是进入了死循环,或者是否哪一段代码有问题 ...

  8. [No000016E]Spring 中获取 request 的几种方法,及其线程安全性分析

    前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性. 原创不易,如果觉得文章对你有帮助,欢迎点赞.评论.文章有疏漏之处,欢迎批评指正. 欢迎 ...

  9. java线程dump分析工具

    jstack和线程dump分析  java程序性能分析之thread dump和heap dump 一.[内存dump] jmap –dump:live,format=b,file=heap.bin ...

随机推荐

  1. mysql-7-join

    #进阶7:连接查询 /* 多表连接:查询的字段来自多个表 按功能分类: 内连接: 等值连接 非等值连接 自连接 外连接: 左外连接 右外连接 全外连接 交叉连接 */ USE girls; SELEC ...

  2. 对offsetof、 container_of宏和结构体的理解

    offsetof 宏 #include<stdio.h> #define offsetoff(type, member)      ((int)&((type*)0)->me ...

  3. Java知识系统回顾整理01基础05控制流程02 switch

    一.switch switch 语句相当于 if else的另一种表达方式 switch可以使用byte,short,int,char,String,enum 注: 每个表达式结束,都应该有一个bre ...

  4. 序列化的JavaScript

    下载 序列化的JavaScript序列化的JavaScript 将JavaScript序列化为包含正则表达式.日期和函数的JSON超集. 概述 这个包中的代码最初是作为表示状态的内部模块.为了扩展它的 ...

  5. TP5自定义路由,为了安全性

    1,入口文件index.php,不要指定任意模块,不然,自定义路由,就不会有任何效果哦 2,在配置文件 route.php  中引进   use think\Router  自带路由文件 3,将路由进 ...

  6. Linux Centos7 安装Docker-CE

    先确保yum 是最新版本 执行: sudo yum update 添加docker源地址 sudo yum-config-manager --add-repo https://download.doc ...

  7. k8s集群添加新得node节点

    服务端操作: 方法一: 获取master的join token kubeadm token create --print-join-command 重新加入节点 kubeadm join 192.16 ...

  8. gitlab 拉代码提示:Your Account has been blocked. fatal: Could not read from remote repository. 最佳解决方案

    今天在脚本服务器上拉取代码,突然发现拉不了代码了,提示: GitLab: Your account has been blocked. fatal: Could not read from remot ...

  9. git-submodule子模块的添加、使用和删除

    目录 添加 使用 更新 删除 hugo添加主题的时候 命令如下: git submodule add https://github.com/samrobbins85/hugo-developer-po ...

  10. Zookeeper入门看这篇就够了

    https://blog.csdn.net/java_66666/article/details/81015302