前言

今天我们来学习一下CopyOnWriteArrayList这个比ArrayList更安全的集合,同时带着以下几个问题去分析源码。

  • CopyOnWriteArrayList如何保证线程安全?
  • CopyOnWriteArrayList有什么优缺点吗?
  • CopyOnWriteArrayList与ArrayList有什么区别吗?
  • 了解CopyOnWriteArraySet吗?

继承关系

public class CopyOnWriteArrayList<E>implements List<E>, RandomAccess, Cloneable,                  java.io.Serializable {//......}
复制代码

首先我们来看一下CopyOnWriteArrayList的继承关系,从源码中可以看出它分别实现了List、RandomAccess、Cloneable以及Serializable,对于我们比较熟悉的应该是List和Serializable了。

核心字段

 //可以看出这是重入锁final transient ReentrantLock lock = new ReentrantLock();//底层数组,可以看出用了volatileprivate transient volatile Object[] array;
复制代码

通过以上两个核心字段,可以猜出CopyOnWriteArrayList内部进行了加锁来保证线程安全。那么它到底是如何实现的呢?我们继续往下分析。

构造方法

 /*** 默认构造方法,初始化了一个空数组*/public CopyOnWriteArrayList() {setArray(new Object[0]);}
复制代码

再来简单看一下其他两个构造方法,如下:

 public CopyOnWriteArrayList(Collection<? extends E> c) {Object[] elements;if (c.getClass() == CopyOnWriteArrayList.class)elements = ((CopyOnWriteArrayList<?>)c).getArray();else {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));}复制代码

修改

  public E set(int index, E element) {//进行加锁final ReentrantLock lock = this.lock;lock.lock();try {//获取旧数组Object[] elements = getArray();//获取旧数据E oldValue = get(elements, index);//如果旧数据与插入的新数据不同if (oldValue != element) {int len = elements.length;//创建了一个新数组Object[] newElements = Arrays.copyOf(elements, len);newElements[index] = element;//设置array为newElementssetArray(newElements);} else {// Not quite a no-op; ensures volatile write semanticssetArray(elements);}return oldValue;} finally {//解锁lock.unlock();}}
复制代码

可以看出在写入数据时通过加锁来保证线程安全,并且通过复制一个新数组。然后修改新数组中index对应的Value值,最后将新数组赋值给CopyOnWriteArrayList的底层数组array,也可以说是将array数组指向新数组,如下图:

对于CopyOnWriteArrayList的set()方法,我们可以这样理解。

  • 获取旧数组
  • 通过复制旧数组创建新数组
  • 在新数组中进行修改
  • array指向新数组

添加

public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {lock.unlock();}}
复制代码

可以看出与set()方法类似,也是通过加锁的方式来保证线程安全。然后创建一个新数组来添加元素,最后将新数组赋值给array。

获取

public E get(int index) {return get(getArray(), index);}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {return (E) a[index];}
复制代码

从get()方法源码中可以看出 ,在从CopyOnWriteArrayList获取元素时并没有进行加锁。那么我们就要思考一个问题了,如果当写入数据时,在没有写完数据的情况下。再去读取数据,那么我们能准确的获得数据吗?而且重点是在get()方法中,是直接对底层数组array进行操作的。那就意味着在我们读取数据时,有可能获得的是旧数据。也就是说CopyOnWriteArrayList并不能保证数据的实时性,主要原因可以从以下几点来思考。

  • 在get()方法并没有进行加锁
  • 读取的是底层数组array的数据

删除

 public E remove(int index) {//加锁final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;E oldValue = get(elements, index);int numMoved = len - index - 1;if (numMoved == 0)setArray(Arrays.copyOf(elements, len - 1));else {Object[] newElements = new Object[len - 1];System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index + 1, newElements, index,numMoved);setArray(newElements);}return oldValue;} finally {//解锁lock.unlock();}}
复制代码

可以看出remove()方法也是一样的,通过加锁来保证线程安全。

其他方法

 /*** 获取底层数组*/final Object[] getArray() {return array;}/*** 设置底层数组*/final void setArray(Object[] a) {array = a;}/*** 获取数组大小*/public int size() {return getArray().length;}/*** 判断数组是否为空*/   public boolean isEmpty() {return size() == 0;}
复制代码

优点与缺点

通过查看源码,我们可以看出CopyOnWriteArrayList最大的优点就是线程安全,也是最突出的优点。它在读取操作时没有加锁,只有当进行写入操作时才会加锁。这也体现出了CopyOnWriteArrayList的一个缺点,那就是无法读取实时数据。因为可能当写操作还没有完成时,就进行了读取操作,那么读取的将是旧数据。

CopyOnWriteArraySet

接下来我们来简单了解一下CopyOnWriteArrayList的表兄弟CopyOnWriteArraySet,主要源码如下。

private final CopyOnWriteArrayList<E> al;public CopyOnWriteArraySet() {al = new CopyOnWriteArrayList<E>();}
复制代码

通过构造方法就可以看出初始化了一个CopyOnWriteArrayList,也就是说CopyOnWriteArraySet内部是用CopyOnWriteArrayList来实现的,我们再来看一下其他几个方法。

public boolean add(E e) {return al.addIfAbsent(e);}
public boolean remove(Object o) {return al.remove(o);}
复制代码

可以看出add()和remove()都是通过CopyOnWriteArrayList来实现。

总结

通过分析源码,我们了解了CopyOnWriteArrayList的内部实现,以及它是如何保证线程安全的,还了解一下CopyOnWriteArraySet。

转载于:https://juejin.im/post/5cbf2d0fe51d456e396318d2

Java基础之CopyOnWriteArrayList相关推荐

  1. 面经——Java基础

    Java基础 ArrayList 和 LinkedList 区别 双亲委派模型以及优点 String是否可以被继承及相关原因 String 和 StringBuffer.StringBuilder 的 ...

  2. Java中的CopyOnWriteArrayList

    介绍: Java中的CopyOnWriteArrayList是List接口的线程安全实现. 它属于java.util.concurrent包,是ArrayList实现的增强版本. 顾名思义, Copy ...

  3. java面试题——java基础(四),java初级面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  4. Java基础篇--设计模式

    目录 前言 设计模式 创建型模式 单例模式 工厂方法模式 抽象工厂模式 建造者模式 原型模式 结构型模式 适配器模式 桥接模式 组合模式 装饰模式 外观模式 亨元模式 代理模式 行为型模式: 访问者模 ...

  5. Java 基础 第3阶段:高级应用——尚硅谷学习笔记(含面试题) 2023年

    Java 基础 第 3 阶段:高级应用--尚硅谷学习笔记(含面试题) 2023 年 Java 基础 第 3 阶段:高级应用--尚硅谷学习笔记(含面试题) 2023 年 第 9 章 异常处理 9.1 异 ...

  6. Java 基础入门,小白提升路线图

    1000+最新Java面试题 获取学习路线资料啦 Java的基础知识就像我们所住的房子的地基,如果地基不稳,上面所盖的楼房再宏伟,也是没人敢去入住的,同理Java的基础不牢固,以后也很难成为真正意义上 ...

  7. java基础知识粗略整理

    Java基础 java的三大特性: 封装:隐藏对象的属性和实现细节,对外仅提供公共访问方式,提高复用性和安全性.在java当中有3中修饰符 public.private.protected赋予不同的访 ...

  8. 字节面试官推荐的一份 Java 基础面试题!太顶了

    Java 基础篇 Java 有哪些特点 并发性的: 你可以在其中执行许多语句,而不必一次执行它 面向对象的:基于类和面向对象的编程语言. 独立性的: 支持一次编写,到处运行的独立编程语言,即编译后的代 ...

  9. 尚学堂JAVA基础学习笔记_2/2

    尚学堂JAVA基础学习笔记_2/2 文章目录 尚学堂JAVA基础学习笔记_2/2 写在前面 第10章 IO技术 1. IO入门 2. IO的API 3. 装饰流 4. IO实战 5. CommonsI ...

最新文章

  1. xftp连接海康摄像头报错:sftp子系统申请已拒绝 请确保ssh连接的sftp子系统设置有效
  2. [云炬创业管理笔记]第十章商业计划书答辩测试1
  3. DEDE文章列表加上序号效果
  4. hset php,HSET命令_视频讲解_用法示例-redis编程词典-php中文网
  5. GBDT迭代决策树的入门教程
  6. html怎么定位到不同的页面,html页面定位到指定位置的4种实现方式
  7. php 解析lrc文件格式,PHP - 四级单词lrc文件解析为txt
  8. 关于JavaScript实现图片预加载的改进
  9. python一个简单的一元二次方程求解的过程
  10. 微信小程序多音频场景处理
  11. 代写品牌故事-品牌故事的结构
  12. 计算机全盘搜索功能不见了,win7搜索功能不见了两种恢复方法(图文)
  13. 联想用u盘重装系统步骤_联想电脑怎样用U盘重装系统?
  14. AR软件开发一个要多少钱?分享AR内容制作市价
  15. 怎么关闭win10虚拟服务器,Win10系统怎么关闭hyper-v虚拟机的功能
  16. 安装a0时服务器名称不显示,现场审计实施系统(AO2011)安装图示
  17. MySQL的几种搜索引擎概述
  18. python 玩玩乐 - moviepy 剪辑视频变成 gif 图
  19. java环境安装(java环境安装教程)
  20. javaScript函数模块详解

热门文章

  1. CTF web题总结--SSRF
  2. python调用gitlab api自动合并分支_Python3 如何使用 GitLab API 进行批量的合并分支
  3. 前端学Markdown
  4. 批量 ping 测试脚本(IP 扫描)
  5. 2.3. 实体(Core Data 应用程序实践指南)
  6. 心中的狂野--最近的感想
  7. 明星软件工程师的十种特质
  8. CYQ.Data 轻量数据访问层(五) 构造数据行
  9. 网管管理的12种方法
  10. spring 全局变量_精华:关于Spring的15点总结