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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/49035.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Profinet转ModbusTCP网关模块的配置与应用详解

Profinet转ModbusTCP网关模块&#xff08;XD-ETHPN20&#xff09;是一种常见的工业通信设备&#xff0c;广泛应用于现代工业自动化系统中。通过使用Profinet转Modbus TCP网关模块&#xff08;XD-ETHPN20&#xff09;将Profinet协议转换成Modbus TCP协议&#xff0c;实现了不同网…

抓包工具Charles

1、抓包的目的 遇到问题需要进行分析 发现bug需要定位 检查数据传输的安全性 接口测试时&#xff0c;开发给的需求文档不详细 在弱网环境下APP的测试 2、Charles是java语言编写的程序&#xff0c;本质是一个代理服务器&#xff0c;通过拦截服务端和客户端的http请求&#xff0…

【SpringCloud】企业认证、分布式事务,分布式锁方案落地-2

目录 高并发缓存三问 - 穿透 缓存穿透 概念 现象举例 解决方案 缓存穿透 - 预热架构 缓存穿透 - 布隆过滤器 布隆过滤器 布隆过滤器基本思想​编辑 了解 高并发缓存三问 - 击穿 缓存击穿 高并发缓存三问 - 雪崩 缓存雪崩 解决方案 总结 为什么要使用数据字典&…

【Dison夏令营 Day 30】如何用 Python 创建平台游戏(下篇)

几十年来&#xff0c;平台游戏一直是深受玩家喜爱的游戏类型&#xff0c;它提供了令人兴奋的挑战和令人怀念的游戏玩法。在本教程中&#xff0c;我们将指导您使用 Python 中的 PyGame 库构建自己的平台游戏。 无论您是希望深入游戏开发的初学者&#xff0c;还是希望探索 Pygame…

【OpenCV C++20 学习笔记】基本图像容器——Mat

【OpenCV C20 学习笔记】基本图像容器——Mat 概述Mat内部结构引用计数机制颜色数据格式 显式创建Mat对象使用cv::Mat::Mat构造函数矩阵的数据项 使用数组进行初始化的构造函数cv::Mat::create函数MATLAB风格的初始化小型矩阵通过复制创建Mat对象 Mat对象的输出其他普通数据项的…

77.WEB渗透测试-信息收集-框架组件识别利用(1)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;76.WEB渗透测试-信息收集- WAF、框架组件识别&#xff08;16&#xff09; java&#xff…

HarmonyOS入门-状态管理

View(UI)&#xff1a;UI渲染&#xff0c;指将build方法内的UI描述和Builder装饰的方法内的UI描述映射到界面。 State&#xff1a;状态&#xff0c;指驱动UI更新的数据。用户通过触发组件的事件方法&#xff0c;改变状态数据。状态数据的改变&#xff0c;引起UI的重新渲染。 装…

C# Nmodbus,EasyModbusTCP读写操作

Nmodbus读写 两个Button控件分别为 读取和写入 分别使用控件的点击方法 ①引用第三方《NModbus4》2.1.0版本 全局 public SerialPort port new SerialPort("COM2", 9600, Parity.None, 8, (StopBits)1); ModbusSerialMaster master; public Form1() port.Open();…

UWA Gears正式上线,助力移动平台性能优化

亲爱的开发者朋友们&#xff0c; 我们非常激动地向大家宣布&#xff0c;UWA最新的无SDK性能分析工具 - UWA Gears&#xff0c;现已正式发布&#xff01;无论您使用的是哪种开发引擎&#xff0c;这款工具都能轻松应对&#xff0c;为您的项目保驾护航。更令人心动的是&#xff0c…

后台管理系统登录安全和权限要求

一、前言 几乎所有的系统都有后台管理系统&#xff0c;后台登录需要账号和密码&#xff0c;后台管理员权限需要有控制。所有管理员的操作都应该有操作日志。 二、存在的问题 现在很多系统只需要账号和密码就能登录&#xff0c;有的还是简单账号和简单密码&#xff0c;就是弱口…

虚拟机之ip配置,ssh连接到虚拟机

右边是我的虚拟机&#xff0c;左边是我使用vscode来连接&#xff08;终端也可以。然后注意vscode配置后点一下刷新&#xff0c;不会自动刷新的QA&#xff09;&#xff08;吐槽一下&#xff0c;虚拟机都不能复制内容呢&#xff0c;确实仿真&#xff0c;centos仿真就是因为没有图…

二手车小程序

本文来自&#xff1a;FastAdmin二手车小程序 - 源码1688 一款基于ThinkPHPFastAdmin开发的原生微信小程序二手车管理系统。 前端小程序码&#xff1a; 后台演示地址&#xff1a; https://facars.site100.cn/OHNYSKzuba.php/carswxsys/sysinit?refaddtabs

视觉巡线小车——STM32+OpenMV(四)

目录 前言 一、整体控制思路 二、代码实现 1.主函数 2.定时器回调函数 总结 前言 通过以上三篇文章已将基本条件实现&#xff0c;本文将结合以上内容&#xff0c;进行综合控制&#xff0c;实现小车的视觉巡线功能。 系列文章请查看&#xff1a;视觉巡线小车——STM32OpenMV系列…

【轨物方案】电表红外抄表物联网装置

对于光伏运维工程师来说&#xff0c;电表抄表是一件并不陌生的工作&#xff0c;不过很多并网电表的RS485通讯接口一般都被占用了&#xff0c;并且电表的外壳也被铅封起来。在这种情况下电站通常采用人工抄表的方式采集电量数据&#xff0c;这种方式费时费力&#xff0c;对电站运…

京东科技集团将在香港发行与港元1:1挂钩的加密货币稳定币

据京东科技集团旗下公司京东币链科技(香港)官网信息&#xff0c;京东稳定币是一种基于公链并与港元(HKD) 1:1挂钩的稳定币&#xff0c;将在公共区块链上发行&#xff0c;其储备由高度流动且可信的资产组成&#xff0c;这些资产安全存放于持牌金融机构的独立账户中&#xff0c;通…

【OpenCV C++20 学习笔记】扫描图片数据

扫描图片数据 应用情景图像数据扫描的难点颜色空间缩减&#xff08;color space reduction&#xff09;查询表 扫描算法计算查询表统计运算时长连续内存3种扫描方法C风格的扫描方法迭代器方法坐标方法LUT方法 算法效率对比结论 应用情景 图像数据扫描的难点 在上一篇文章《基…

OAK-FFC 分体式相机使用入门介绍

概述 OAK FFC 主控板和多种可选配镜头模组非常适合灵活的搭建您的3D人工智能产品原型。由于镜头是分体式的&#xff0c;因此你可以根据需要测量的距离&#xff0c;自定义深度相机安装基线&#xff0c;并根据你的项目要求&#xff08;分辨率、快门类型、FPS、光学元件&#xff…

FPGA教程|零基础到实战的全方位指导

在数字化时代&#xff0c;FPGA&#xff08;现场可编程门阵列&#xff09;作为一种灵活的硬件编程平台&#xff0c;正逐渐成为电子工程师和计算机科学家必备的技能之一。无论你是电子专业的学生&#xff0c;还是职场中的工程师&#xff0c;掌握FPGA开发技术&#xff0c;都将为你…

前端与HTML

前端与HTML 什么是前端&#xff1f; 解决GUI人机交互问题跨终端&#xff08;PC/移动浏览器&#xff09;&#xff08;客户端小程序&#xff09;&#xff08;VR/AR等&#xff09;Web技术栈 前端工程师&#xff1a;利用web技术栈解决多端图形界面用户交互的工程师 前端技术栈 …

CDGA|数据治理:安全如何贯穿数据供给、流通、使用全过程

随着信息技术的飞速发展&#xff0c;数据已经成为企业运营、社会管理和经济发展的核心要素。然而&#xff0c;数据在带来巨大价值的同时&#xff0c;也伴随着诸多安全风险。因此&#xff0c;数据治理的重要性日益凸显&#xff0c;它不仅仅是对数据的简单管理&#xff0c;更是确…