CopyOnWriteArrayList引入

模拟传统的ArrayList出现线程不安全的现象

public class Demo1 {
public static void main(String[] args) {
//List<String> list = new CopyOnWriteArrayList<>();
List<String> list = new ArrayList<>(); //开启50个线程往ArrayList中添加数据
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
} }
}

运行结果如下:由于fail-fast机制的存在,抛出了modcount修改异常的错误(modcount是ArrayList源码中的一个变量,用来表示修改的次数,因为ArrayList不是为并发情况而设计的集合类)



如何解决该问题呢?

方式一:可以使用Vector集合,Vector集合是线程安全版的ArrayList,其方法都上了一层synchronized进行修饰,采取jvm内置锁来保证其并发情况下的原子性、可见性、有序性。但同时也带来了性能问题,因为synchronized一旦膨胀到重量级锁,存在用户态到和心态的一个转变,多线程的上下文切换会带来开销。另一个问题是Vector集合的扩容没有ArrayList的策略好

List<String> list = new Vector<>();

方式二:使用Collections.synchronizedList

List<String> list = Collections.synchronizedList(new ArrayList<>());

方式三:采用JUC提供的并发容器,CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList浅析

和ArrayList一样,其底层数据结构也是数组,加上transient不让其被序列化,加上volatile修饰来保证多线程下的其可见性和有序性

先来看看其构造函数是怎么一回事

    public CopyOnWriteArrayList() {
//默认创建一个大小为0的数组
setArray(new Object[0]);
} final void setArray(Object[] a) {
array = a;
} public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
//如果当前集合是CopyOnWriteArrayList的类型的话,直接赋值给它
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
//否则调用toArra()将其转为数组
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
//设置数组
setArray(elements);
} public CopyOnWriteArrayList(E[] toCopyIn) {
//将传进来的数组元素拷贝给当前数组
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

在来看看其读数据的几个操作,可见都没上锁,这就奇怪了,那如何去保证线程安全呢?

    final Object[] getArray() {
return array;
}
public int size() {
return getArray().length;
}
public boolean isEmpty() {
return size() == 0;
}
public int indexOf(E e, int index) {
Object[] elements = getArray();
return indexOf(e, elements, index, elements.length);
}
public int lastIndexOf(Object o) {
Object[] elements = getArray();
return lastIndexOf(o, elements, elements.length - 1);
} ........

在来看看其修改时的add函数

    public boolean add(E e) {
//使用ReentrantLock上锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//调用getArray()获取原来的数组
Object[] elements = getArray();
int len = elements.length;
//复制老数组,得到一个长度+1的数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//添加元素,在用setArray()函数替换原数组
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

可见其修改操作是基于fail-safe机制,像我们的String一样,不在原来的对象上直接进行操作,而是复制一份对其进行修改,另外此处的修改操作是利用Lock锁进行上锁的,所以保证了线程安全问题。

在来看看remove操作,看是不是如此做的

    public boolean remove(Object o) {
Object[] snapshot = getArray();
int index = indexOf(o, snapshot, 0, snapshot.length);
return (index < 0) ? false : remove(o, snapshot, index);
} private boolean remove(Object o, Object[] snapshot, int index) {
final ReentrantLock lock = this.lock;
//上锁
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) findIndex: {
int prefix = Math.min(index, len);
for (int i = 0; i < prefix; i++) {
if (current[i] != snapshot[i] && eq(o, current[i])) {
index = i;
break findIndex;
}
}
if (index >= len)
return false;
if (current[index] == o)
break findIndex;
index = indexOf(o, current, index, len);
if (index < 0)
return false;
}
//复制一个数组
Object[] newElements = new Object[len - 1];
System.arraycopy(current, 0, newElements, 0, index);
System.arraycopy(current, index + 1,
newElements, index,
len - index - 1);
//替换原数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

可见其思路是一致的,我们在与ArrayList去对比一下,可见其效率比ArrayList低不少,毕竟多线程场景下,其每次都是要在原数组基础上复制一份在操作耗内存和时间,而ArrayList只是容量满了进行扩容,因此在非多线程的场景下还是用ArrayList吧。

这也解决了我之前的疑问,为啥还学ArrayList呢,JUC版的CopyOnWriteArrayList可以干ArrayList干不了的事,咱们直接用CopyOnWriteArrayList不也挺香。

小结

  • CopyOnWriteArrayList适合于多线程场景下使用,其采用读写分离的思想,读操作不上锁,写操作上锁,且写操作效率较低
  • CopyOnWriteArrayList基于fail-safe机制,每次修改都会在原先基础上复制一份,修改完毕后在进行替换
  • CopyOnWriteArrayList采用的是ReentrantLock进行上锁。

浅析CopyOnWriteArrayList的更多相关文章

  1. Collections.synchronizedList 、CopyOnWriteArrayList、Vector介绍、源码浅析与性能对比

    ## ArrayList线程安全问题 众所周知,`ArrayList`不是线程安全的,在并发场景使用`ArrayList`可能会导致add内容为null,迭代时并发修改list内容抛`Concurre ...

  2. Android AIDL浅析及异步使用

    AIDL:Android Interface Definition Language,即 Android 接口定义语言. AIDL 是什么 Android 系统中的进程之间不能共享内存,因此,需要提供 ...

  3. SQL Server on Linux 理由浅析

    SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...

  4. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  5. 高性能IO模型浅析

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking  ...

  6. netty5 HTTP协议栈浅析与实践

      一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...

  7. Jvm 内存浅析 及 GC个人学习总结

    从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...

  8. 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler

    熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...

  9. Netty构建分布式消息队列实现原理浅析

    在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...

随机推荐

  1. ubuntu 下python出现pkg: error processing package *python* 解决之道

    1.linux有些自带程序很多是python写的,自带的python2也最好不要升级,不然会有很多问题 2.如果遇到 pkg: error processing package *python* (- ...

  2. 【笔记3-27】Python语言基础

    流程控制语句 if语句 input() if-else if-elif-else

  3. TorchScript简介

    本教程是对TorchScript的简介,TorchScript是PyTorch模型(nn.Module的子类)的中间表示,可以在高性能环境(例如C )中运行. 在本教程中,我们将介绍: PyTorch ...

  4. coding++:error Could not read JSON: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object

    Spring源码中是使用容器中的ObjectMapper对象进行序列化和反序列化. 当我们将自定义的ObjectMapper对象放入IOC容器中后,会自动覆盖SpringBoot自动装载的Object ...

  5. coding++:Spring Boot全局事务解释及使用(一)

    Spring 事务的入口: TxAdviceBeanDefinitionParser 解释 <tx:advice/> 这里将解析tx的配置. @Override protected Cla ...

  6. 【动态规划】最佳加法表达式(百练oj4152)

    总时间限制: 1000ms 内存限制: 65536kB 描述 给定n个1到9的数字,要求在数字之间摆放m个加号(加号两边必须有数字),使得所得到的加法表达式的值最小,并输出该值.例如,在1234中摆放 ...

  7. 【电商】性能测试网站搭建:XAMPP1.8+DBShop1.3

    1.安装准备 1.1软件版本 XAMPP的版本:XAMPP 1.8.2 DBShop的版本:DBShop 1.3   1.2.安装环境 我的环境:win10 win7,win10都可以运行的,安装步骤 ...

  8. Hadoop(三)HDFS写数据的基本流程

    HDFS写数据的流程 HDFS shell上传文件a.txt,300M 对文件分块,默认每块128M. shell向NameNode发送上传文件请求 NameNode检测文件系统目录树,看能否上传 N ...

  9. 基于 Spring Cloud 的微服务架构实践指南(下)

    show me the code and talk to me,做的出来更要说的明白 本文源码,请点击learnSpringCloud 我是布尔bl,你的支持是我分享的动力! 一.引入 上回 基于 S ...

  10. Qt开发环境搭建 - Windows + VS2010 + VS插件

    Qt 开发环境搭建 - Windows+VS2010+VS插件 1.Qt在Windows平台下的三种开发环境 方案 编辑器 编译器 调试器 一 Qt Creator MinGW GDB 二 Qt Cr ...