当前位置: 首页 > news >正文

18.jdk源码阅读之CopyOnWriteArrayList

1. 写在前面

CopyOnWriteArrayList 是 Java 中的一种线程安全的 List 实现,基于“写时复制”(Copy-On-Write)机制。下面几个问题大家可以先思考下,在阅读源码的过程中都会解答:

  1. CopyOnWriteArrayList 适用于哪些场景?
  2. CopyOnWriteArrayList 如何保证线程安全?
  3. CopyOnWriteArrayList 的优缺点是什么?
  4. 为什么 CopyOnWriteArrayList 的迭代器是安全的?
  5. CopyOnWriteArrayList 与 ArrayList 有什么区别?
  6. 如何在 CopyOnWriteArrayList 中进行批量操作?
  7. 如何在 CopyOnWriteArrayList 中进行批量操作?

2. 全局视角

在这里插入图片描述

2.1 RandomAccess

RandomAccess 是 Java 集合框架中的一个标识接口(Marker Interface),它定义在 java.util 包中。实现 RandomAccess 接口的类表示其支持快速(通常是常数时间复杂度 O(1))的随机访问操作。
RandomAccess 接口本身是一个空接口,没有任何方法。其定义如下:

package java.util;public interface RandomAccess {
}

2.1.1 标识快速随机访问能力

  • 实现 RandomAccess 接口的类表明它们支持快速的随机访问操作。具体来说,这意味着 get(int index) 和 set(int index, E element) 操作的时间复杂度通常是 O(1)。
  • 例如,ArrayList 实现了 RandomAccess 接口,因为它基于数组实现,能够在常数时间内访问任意索引位置的元素。

2.1.2 优化算法选择

  • 一些算法可以根据集合是否实现了 RandomAccess 接口来选择更合适的实现方式。例如,Collections 类中的一些方法会检查传入的列表是否实现了 RandomAccess 接口,从而决定是使用基于索引的循环还是使用迭代器进行遍历。
  • 例如,Collections.sort 方法在对列表进行排序时,如果列表实现了 RandomAccess 接口,它将使用基于索引的访问方式,否则将使用迭代器。

2.1.3 如何使用 RandomAccess 接口来优化算法

以下是一个简单的示例,展示了如何使用 RandomAccess 接口来优化算法选择:

import java.util.*;public class RandomAccessExample {public static void main(String[] args) {List<Integer> arrayList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));List<Integer> linkedList = new LinkedList<>(Arrays.asList(1, 2, 3, 4, 5));printList(arrayList);printList(linkedList);}public static void printList(List<Integer> list) {if (list instanceof RandomAccess) {System.out.println("Using index-based loop");for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}} else {System.out.println("Using iterator-based loop");for (Integer element : list) {System.out.println(element);}}}
}

在这个示例中,printList 方法根据列表是否实现了 RandomAccess 接口来选择不同的遍历方式:

  • 如果列表实现了 RandomAccess 接口(如 ArrayList),则使用基于索引的循环进行遍历。
  • 如果列表没有实现 RandomAccess 接口(如 LinkedList),则使用迭代器进行遍历。

看到这里你肯定想问 基于索引的循环进行遍历和使用迭代器进行遍历 有什么区别?
在 Java 中,基于索引的循环遍历和使用迭代器进行遍历是两种常见的遍历集合的方法。这两种方法在性能、可读性和使用场景上各有优缺点。下面我们详细比较一下这两种遍历方法的区别。

2.1.3.1 基于索引的循环遍历

基于索引的循环遍历通常使用 for 循环,通过索引访问集合中的元素。例如:

List<String> list = Arrays.asList("A", "B", "C", "D");
for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));
}

优点

  • 直接访问:对于实现了 RandomAccess 接口的集合(如 ArrayList),基于索引的访问是非常高效的,通常是 O(1) 时间复杂度。
  • 简洁:代码简洁明了,易于理解。
    缺点
  • 性能问题:对于不支持快速随机访问的集合(如 LinkedList),基于索引的访问可能会非常低效,因为每次访问元素都需要从头遍历链表,时间复杂度为 O(n)。
  • 不安全的并发修改:在遍历过程中,如果集合被修改(添加或删除元素),会导致 ConcurrentModificationException。
2.1.3.2 使用迭代器进行遍历

使用迭代器进行遍历通常通过 Iterator 或增强的 for-each 循环来实现。例如:

List<String> list = Arrays.asList("A", "B", "C", "D");
for (String element : list) {System.out.println(element);
}// 或者显式使用 Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {System.out.println(iterator.next());
}

优点

  • 适用于所有集合:无论集合是否支持快速随机访问,使用迭代器进行遍历都是高效的。对于链表等不支持随机访问的集合,迭代器的性能优于基于索引的访问。
  • 安全的并发修改:迭代器提供了 fail-fast 机制,可以检测到并发修改并抛出 ConcurrentModificationException,从而避免潜在的并发问题。
  • 简洁的语法:增强的 for-each 循环语法简洁,易于阅读和维护。
    缺点
  • 无法直接访问索引:迭代器不提供直接访问索引的方法。如果需要访问元素的索引,必须额外维护一个计数器。
  • 额外的开销:迭代器可能会引入一些额外的开销,尤其是在创建迭代器对象时。
2.1.3.3 性能比较

对于实现了 RandomAccess 接口的集合(如 ArrayList),基于索引的访问通常是最快的,因为它支持常数时间复杂度的随机访问。
对于不支持快速随机访问的集合(如 LinkedList),使用迭代器进行遍历通常更高效,因为链表的随机访问时间复杂度为 O(n),而迭代器可以通过链表的节点引用进行顺序访问,时间复杂度为 O(1)。

2.1.3.4 选择建议
  • 如果你确定集合实现了 RandomAccess 接口(如 ArrayList),并且需要频繁的随机访问,可以使用基于索引的循环进行遍历。
  • 如果你不确定集合的实现类型,或者集合可能是链表(如 LinkedList),建议使用迭代器进行遍历,以获得更好的性能和安全性。
  • 在并发环境中,如果集合可能在遍历过程中被修改,建议使用迭代器进行遍历,以利用其 fail-fast 机制检测并发修改。

3. 从使用说起

以下是一个简单的示例,展示了 CopyOnWriteArrayList 的基本用法:

import java.util.concurrent.CopyOnWriteArrayList;public class CopyOnWriteArrayListExample {public static void main(String[] args) {// 创建一个 CopyOnWriteArrayList 实例CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();// 添加元素list.add("A");list.add("B");list.add("C");// 读取元素System.out.println("Element at index 1: " + list.get(1));// 遍历元素System.out.println("Elements in the list:");for (String s : list) {System.out.println(s);}// 删除元素list.remove("B");// 再次遍历元素System.out.println("Elements in the list after removal:");for (String s : list) {System.out.println(s);}// 使用迭代器遍历元素System.out.println("Using iterator to traverse the list:");for (String s : list) {System.out.println(s);}}
}

3.1 创建 CopyOnWriteArrayList 实例

你可以通过无参构造方法或通过传入一个已有集合来创建 CopyOnWriteArrayList 实例。

3.2 添加元素

使用 add 方法可以向列表中添加元素

3.3 读取元素

使用 get 方法可以读取指定索引位置的元素

3.4 删除元素

使用 remove 方法可以删除指定的元素或指定索引位置的元素

3.5 遍历元素

可以使用增强的 for 循环或迭代器进行遍历

4. CopyOnWriteArrayList 的工作原理是什么?

CopyOnWriteArrayList 基于“写时复制”机制。当进行写操作(如添加、删除、更新)时,它会复制一个新的底层数组,在新的数组上进行修改,然后将新的数组设置为当前数组。读操作则直接访问当前数组,不需要加锁。

5. CopyOnWriteArrayList 适用于哪些场景?

CopyOnWriteArrayList 适用于读操作远多于写操作的场景。由于写操作需要复制数组,开销较大,因此在写操作频繁的场景下性能较差。但在读操作频繁、写操作较少的场景下,CopyOnWriteArrayList 可以提供非常高效的并发读性能。

6. CopyOnWriteArrayList 如何保证线程安全?

CopyOnWriteArrayList 通过在每次写操作时复制底层数组来保证线程安全。由于每次写操作都会创建一个新的数组,读操作始终访问的是一个稳定的、不变的数组,因此不需要加锁。这种机制避免了读写锁的开销,提高了读操作的性能。

7. CopyOnWriteArrayList 的优缺点是什么?

7.1 优点

  • 高效的并发读性能:读操作不需要加锁,可以并发执行。
  • 线程安全:通过写时复制机制保证线程安全。
  • 迭代器安全:迭代过程中不需要担心 ConcurrentModificationException。

7.2 缺点

  • 写操作开销大:每次写操作都会复制底层数组,开销较大。
  • 内存消耗高:频繁的写操作会导致大量的数组复制,增加内存消耗。
  • 不适合写操作频繁的场景:在写操作频繁的场景下性能较差。

8. CopyOnWriteArrayList 与 ArrayList 有什么区别?

  • 线程安全性:CopyOnWriteArrayList 是线程安全的,而 ArrayList 不是。
  • 写操作:CopyOnWriteArrayList 的写操作会复制底层数组,开销较大;ArrayList 的写操作直接修改底层数组。
  • 读操作:CopyOnWriteArrayList 的读操作不需要加锁,可以并发执行;ArrayList 在多线程环境下读操作需要外部同步。
  • 迭代器:CopyOnWriteArrayList 的迭代器基于数组快照,不会抛出 ConcurrentModificationException;ArrayList 的迭代器在检测到结构性修改时会抛出 ConcurrentModificationException。

9. 如何在 CopyOnWriteArrayList 中进行批量操作?

由于 CopyOnWriteArrayList 的写操作开销较大,批量操作(如批量添加、删除)可能会导致性能问题。可以通过以下方式优化批量操作:

// 批量添加元素
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
List<String> elementsToAdd = Arrays.asList("A", "B", "C");
list.addAll(elementsToAdd);// 批量删除元素
List<String> elementsToRemove = Arrays.asList("A", "B");
list.removeAll(elementsToRemove);

系列文章

1.JDK源码阅读之环境搭建

2.JDK源码阅读之目录介绍

3.jdk源码阅读之ArrayList(上)

4.jdk源码阅读之ArrayList(下)

5.jdk源码阅读之HashMap

6.jdk源码阅读之HashMap(下)

7.jdk源码阅读之ConcurrentHashMap(上)

8.jdk源码阅读之ConcurrentHashMap(下)

9.jdk源码阅读之ThreadLocal

10.jdk源码阅读之ReentrantLock

11.jdk源码阅读之CountDownLatch

12.jdk源码阅读之CyclicBarrier

13.jdk源码阅读之Semaphore

14.jdk源码阅读之线程池(上)

15.jdk源码阅读之线程池(下)

16.jdk源码阅读之ArrayBlockingQueue

17.jdk源码阅读之LinkedBlockingQueue

相关文章:

18.jdk源码阅读之CopyOnWriteArrayList

1. 写在前面 CopyOnWriteArrayList 是 Java 中的一种线程安全的 List 实现&#xff0c;基于“写时复制”&#xff08;Copy-On-Write&#xff09;机制。下面几个问题大家可以先思考下&#xff0c;在阅读源码的过程中都会解答&#xff1a; CopyOnWriteArrayList 适用于哪些场景…...

美股:AMD展现乐观前景,挑战AI加速器市场霸主

在科技行业的激烈竞争中&#xff0c;AMD公司近期发布了对当前季度收入的乐观预测&#xff0c;显示出其新推出 一、AMD第三季度营收预期超越分析师平均预期 AMD在周二的声明中预计&#xff0c;第三季度营收将达到约67亿美元&#xff0c;这一数字超出了分析师此前平均预期的66.…...

如何提高计算机视觉技术在复杂环境和低光照条件下的物体识别准确率?

要在复杂环境和低光照条件下提高计算机视觉技术的物体识别准确率&#xff0c;可以采取以下几个方法&#xff1a; 数据增强&#xff1a;在训练集中添加各种复杂环境和低光照条件下的图片&#xff0c;通过增加数据的多样性&#xff0c;使算法能够更好地适应各种场景。 预处理&am…...

ubuntu cmake使用自己版本的qt

给一篇文章参考 https://blog.csdn.net/bank_dreamer/article/details/138678909 自己使用的范例 set(Qt5_DIR "/home/peak/Qt5.14.0/5.14.0/gcc_64/lib/cmake/Qt5")# 设置Qt5的安装目录 #set(CMAKE_PREFIX_PATH "/home/peak/Qt5.14.0")find_package(Qt5…...

Python基础知识笔记---保留字

保留字&#xff0c;也称关键字&#xff0c;是指被编程语言内部定义并保留使用的标识符。 一、保留字概览 二、保留字用途 1. False&#xff1a;表示布尔值假。 2. None&#xff1a;表示空值或无值。 3. True&#xff1a;表示布尔值真。 4. not&#xff1a;布尔逻辑操作符…...

Python面试整理-Web开发

在Python中,Web开发可以利用多种强大的框架和库来构建从简单的静态网页到复杂的动态Web应用。以下是几种流行的Python Web开发框架和相关技术的概述: 1. Flask Flask 是一个轻量级的Web应用框架,它非常灵活,适用于小型到中型项目,或作为构建微服务的基础。Flask的核心非常…...

民大食堂用餐小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商家管理&#xff0c;档口号管理&#xff0c;商家餐品管理&#xff0c;餐品种类管理&#xff0c;购物车管理&#xff0c;订单信息管理 微信端账号功能包括&#xff1a;系统首页&a…...

Linux系统编程(4):消息队列

Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。 而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室 以及 BSD&#xff08;加州大学伯克利分校的伯克利软件发布中心&#xff09;&#xff0c; 他们在进程间通信方面的侧重点有所不同&#xff1b; 前…...

【初阶数据结构篇】单链表的实现(赋源码)

文章目录 单链表的实现代码位置概念与结构概念&#xff1a;结构&#xff1a; 链表的性质链表的分类单链表的实现单链表的创建和打印及销毁单链表的创建单链表的打印单链表的销毁 单链表的插入单链表头插单链表尾插单链表在指定位置之前插入数据单链表在指定位置之后插入数据 单…...

LeetCode 2844.生成特殊数字的最少操作(哈希表 + 贪心)

给你一个下标从 0 开始的字符串 num &#xff0c;表示一个非负整数。 在一次操作中&#xff0c;您可以选择 num 的任意一位数字并将其删除。请注意&#xff0c;如果你删除 num 中的所有数字&#xff0c;则 num 变为 0。 返回最少需要多少次操作可以使 num 变成特殊数字。 如…...

昇思MindSpore 应用学习-基于 MindSpore 实现 BERT 对话情绪识别

基于 MindSpore 实现 BERT 对话情绪识别 模型简介 BERT全称是来自变换器的双向编码器表征量&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;&#xff0c;它是Google于2018年末开发并发布的一种新型语言模型。与BERT模型相似的预训练语言模…...

【初阶数据结构篇】顺序表和链表算法题

文章目录 顺序表算法题移除元素删除有序数组中的重复项合并两个有序数组 链表算法题移除链表元素反转链表链表的中间结点合并两个有序链表链表分割链表的回文结构 顺序表算法题 不熟悉顺序表的可以先了解一下 顺序表实现方法 移除元素 给你一个数组 nums 和一个值 val&#x…...

使用weex进行APP混合开发

Weex 是一个用于构建高性能原生应用的框架&#xff0c;它使用 Vue.js 的语法和组件模型&#xff0c;允许开发者使用 HTML、CSS 和 JavaScript 来编写应用&#xff0c;同时能够编译成原生应用。Weex 主要由阿里巴巴集团开发&#xff0c;并且已经被多个公司采用。 下面是使用 We…...

C++stl大根堆/小根堆的创建与记忆

priority_queue<int, vector<int>, greater<int>> heap; 这行代码在 C 中声明了一个优先队列 heap&#xff0c;其元素类型为 int&#xff0c;使用 vector<int> 作为其底层容器&#xff0c;并且指定了 greater<int> 作为比较函数对象。 这里的关…...

visual studio性能探测器使用案列

visual studio性能探测器使用案列 在visual studio中&#xff0c;我们可以使用自带的工具对项目进行性能探测&#xff0c;具体如下 1.选择性能探查器 Vs2022/Vs2019中打开方式&#xff1a; Vs2017打开方式&#xff1a; 注意最好将解决方案配置为&#xff1a;Release Debu…...

redis的代码开发

redis是什么? 前提:官网地址https://redis.io 1.Redis是一个开源的,key,value格式的,内存型数据结构存储系统;它可用作数据库、缓存和消息中间件。 value支持多种类型的数据结构如strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglo…...

嗷呜,就问你接不接?

...

避免过拟合,参数大模型强,正则让模型不要走偏

1、加入惩罚项L1【绝对值】 和L2【默认 平方】&#xff0c;降低噪音的影响&#xff0c;减少权重W的值 2、丢弃法 层与层之间加入噪音&#xff0c;只能在全连接层使用 无偏差加入噪音 p为丢弃的概率 x 当概率p是0 否则为除以(1-p) 丢弃概率p 一般为0.1 0.5 def drop_out(x…...

vue+element-ui的列表查询条件/筛选条件太多以下拉选择方式动态添加条件(支持全选、反选、清空)

1、此功能已集成到TQueryCondition组件中 2、最终效果 3、具体源码(新增moreChoose.vue) <template><el-popoverpopper-class"t_query_condition_more":bind"popoverAttrsBind"ref"popover"v-if"allcheckList.length>0"…...

LLM的训练与推断

LLM的训练与推断 目前比较流行的大模型一般都是自回归模型。在推理时&#xff0c;它类似于RNN&#xff0c;每次计算下一个token的概率。也就是说&#xff0c;如果除去最开始的输入情况下&#xff0c;最终推理长度为n的话&#xff0c;就需要计算n次。但是训练却是并行化的。 在…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...