Java容器源码重点回顾——CopyOnWriteArrayList

news/2024/11/28 15:42:11/

1. CopyOnWriteArrayList概述

之前介绍过ArrayList,但是我们知道ArrayList是线程不安全的。如果多个线程同时写数据,就会抛出ConcurrentModificationException。然后我们又学过Vector,它的实现方式是在方法中都加入synchronized关键字,但是这会导致效率低下,并且只有原子方法下能够保证线程安全。

后面,我们又学习了锁,比较常用的是ReentrantLock。为了适用读多写少的场景,出现了ReentrantReadWriteLock。虽然相比于ReentrantLock,ReentrantReadWriteLock虽然在一定程度上提高了读写性能,比较适合读多写少的场景,但是在有线程持有写锁的时候,读线程还是会被阻塞

因此,CopyOnWriteArrayList就是针对了读写场景下互斥的缺陷,进行优化。它在每次需要进行修改的时候,先拷贝一份原来的数组,在拷贝的新数组上进行修改,最后再将引用指向新数组。这样就能保证,有线程在修改时,读线程不会被阻塞。提高了读写的性能。

但是,它也有缺陷

  1. 每次更新都要拷贝原来的数组,如果原数组比较大,就会占用2倍的空间;
  2. 无法做到实时一致性,只能做到最终一致性。因为在新数组上进行修改时,读线程是没法感知的。只有更新了引用的指向之后,才能获取到最新值。

2. 成员变量

public class CopyOnWriteArrayList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable {// 控制写操作线程安全的锁final transient ReentrantLock lock = new ReentrantLock();// 用来存储数据的数组private transient volatile Object[] array;
}

CopyOnWriteArrayList的成员变量很简单,只有两个。

  • lock: 这个成员变量是用来保证写线程的线程安全的,要保证在同一个时刻,不能有多个线程修改数组;
  • array:这个成员变量是用来存储数据的。需要特别注意的是它使用volatile进行修饰,这个会在后面进行讲解。

3. get方法

    public E get(int index) {return get(getArray(), index);}final Object[] getArray() {return array;}private E get(Object[] a, int index) {return (E) a[index];}

CopyOnWriteArrayList的get方法我们可以看到很简单,也不需要加锁什么的,就是简单地从数组读取返回,因为写线程并不会操作原来的数组。

4. add方法

    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();  // 解锁}}

在add方法中,我们可以看到首先会使用ReentrantLock进行加锁,这是为了保证写线程的线程安全,避免多个写线程复制多次数组。然后就在新数组上进行修改,修改完成后再将指针指向新数组。add方法流程如下图:

在这里插入图片描述

需要注意的是:之前讲过array数组是使用volatile修饰的,根据volatile的happens-before规则,写线程对于数组的引用修改是对于读线程可见的。 这里的volatile只能保证引用修改的可见性,不能保证数组元素修改的可见性。所以读线程并不能立马感知到修改,可能会隔好几秒才能感知到。

参考文章:并发容器之CopyOnWriteArrayList


http://www.ppmy.cn/news/4061.html

相关文章

Redis——好友关注、共同关注、Feed流推送

1. 好友关注 在探店图文的详情页面中&#xff0c;可以关注发布笔记的作者&#xff1a; 进到探店笔记详情页&#xff0c;会发出两个请求&#xff0c;1是判断是否已经关注&#xff0c;2是尝试关注用户的请求。 关注是User之间的关系&#xff0c;是博主与粉丝的关系&#xff0c;…

Java也可以轻松编写并发程序

如今&#xff0c;多核处理器在服务器&#xff0c;台式机及笔记本电脑上已经很普遍了&#xff0c;同时也被应用在更小的设备上&#xff0c;比如智能手机和平板电脑。这就开启了并发编程新的潜力&#xff0c;因为多个线程可以在多个内核上并发执行。在应用中要实现最大性能的一个…

深入剖析Linux RCU原理(一)初窥门径

说明&#xff1a; Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 RCU, Read-Copy-Update&#xff0c;是Linux内核中的一种同步机制。RCU常被描述为读写锁的替代品&#xf…

【Java笔记】 深入理解序列化和反序列化

深入理解序列化和反序列化 文章目录深入理解序列化和反序列化1.是什么2.为什么3.怎么做3.1 实现Serializable接口3.2 实现Externalizable接口3.3 注意知识点3.4 serialVersionUID的作用4 扩展1.是什么 序列化&#xff1a;就是讲对象转化成字节序列的过程。 反序列化&#xff…

Redis框架(十五):大众点评项目 共同关注方案实现?双指针筛选DB数据:Redis取交集

大众点评项目 好友关注 共同关注需求&#xff1a;好友关注 共同关注业务逻辑展示点击关注功能实现判断当前用户是否关注了此博主共同好友列表查询业务逻辑实现双指针筛选DB数据Redis取交集总结SpringCloud章节复习已经过去&#xff0c;新的章节Redis开始了&#xff0c;这个章节…

数据结构---各类排序算法详解

Lesson6–排序 文章目录Lesson6--排序一、.排序的概念及其应用1.1排序的概念1.2常见的排序算法二、.常见排序算法的实现2.1插入排序2.1.1插入排序的基本思想&#xff1a;2.1.2直接插入排序&#xff1a;2.1.3 直接插入排序代码实现2.1.4希尔排序&#xff08;缩小增量排序&#x…

NR RedCap UE关键技术与标准化进展

【摘 要】为满足工业无线传感器、视频监控和可穿戴设备等中端物联网用例的需求,3GPP Rel-17研究定义5G新空口(NR)低复杂度低成本终端(即RedCap UE)。介绍了5G RedCap UE的主要用例与标准化目标,分析了RedCap UE降低复杂度与成本、终端节能、终端指示与识别等关键技术,分…

Mac如何做才能彻底清理垃圾

Mac磁盘空间又爆满了&#xff1f;系统运行又卡了&#xff1f;你的Mac需要清理内存缓存垃圾啦&#xff01;本文教会你如何彻底清除Mac垃圾文件&#xff0c;释放存储空间。 现在&#xff0c;Mac电脑最顶配的硬盘可达2TB。这么大的容量&#xff0c;应该够用了吧&#xff1f;可真正…