18.jdk源码阅读之CopyOnWriteArrayList

news2024/11/16 13:03:28

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.coloradmin.cn/o/1949856.html

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

相关文章

【微信小程序实战教程】之微信小程序 WXML 语法详解

WXML语法基础 从本章开始&#xff0c;我们就正式进入到了小程序项目开发学习的初级阶段&#xff0c;本章将介绍小程序的界面构成。有过网页开发学习经历的同学都知道&#xff0c;网页开发所使用的技术是HTML、CSS和JS&#xff0c;其中HTML用于描述整个网页的结构&#xff0c;也…

谷粒商城实战笔记-59-商品服务-API-品牌管理-使用逆向工程的前后端代码

文章目录 一&#xff0c; 使用逆向工程生成的代码二&#xff0c;生成品牌管理菜单三&#xff0c;几个小问题 在本次的技术实践中&#xff0c;我们利用逆向工程的方法成功地为后台管理系统增加了品牌管理功能。这种开发方式不仅能快速地构建起功能模块&#xff0c;还能在一定程度…

【前端学习】CSS三大特性

CSS三大特性 CSS的三大特性是为了化简代码、定位问题并且解决问题 继承性 继承性特点&#xff1a; 子级默认继承父级的文字控制属性。注意&#xff1a;如果标签自己有样式则生效自己的样式&#xff0c;不继承。 <!DOCTYPE html> <html lang"en"><…

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

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

openFeign配置okhttp

原来的项目出现了性能问题&#xff0c;老大不知道怎么的&#xff0c;让我改openFeign线程池为okhttp&#xff0c;说原生的不支持线程池性能比较差。 原openFeign配置文章地址 一、pom文件 <dependency><groupId>org.springframework.cloud</groupId><arti…

OLLaMA搭建本地大模型

LLMChain是一个简单的链&#xff0c;接受一个提示模板&#xff0c;使用用户输入格式化它并从LLM返回响应。 其中&#xff0c;prompt_template是一个非常关键的组件&#xff0c;可以让你创建一个非常简单的链&#xff0c;它将接收用户输入&#xff0c;使用它格式化提示&#xff…

通信原理-思科实验四:静态路由项配置实验

实验四 静态路由项配置实验 一&#xff1a;实验内容 二&#xff1a;实验目的 三、实验原理 四、实验步骤 选择三个2811型号的路由器 R1、R2、R3 路由器默认只有两个快速以太网接口&#xff0c;为路由器R1和R3增加快速以太网接口模块NM-1FE-TX&#xff0c;安装后检查路由器的接…

C++入门基础:C++中的循环语句

循环语句是编程语言中用来重复执行一段代码直到满足特定条件的一种控制结构。它们对于处理需要重复任务的场景非常有用&#xff0c;比如遍历数组、累加数值、重复执行某项操作直到满足条件等。 但是在使用循环语句的时候需要注意下哈&#xff0c;有时候一不小心会构成死循环或者…

学习小型gpt源码(自用)

数据集构建_哔哩哔哩_bilibili &#xff08;b站上有一系列课&#xff0c;从数据处理到模型构建和训练使用&#xff09; 什么是batch&#xff1f; 为什么一个batch内的句子要一样长&#xff1f; 不同batch的长度可以不一样&#xff0c;但是同一个batch内长度一样&#xff01;…

IDEA缓存和索引

IDEA缓存和索引 —2020年06月10日 IntelliJ IDEA首次加载项目的时候。都会创建索引&#xff0c;而创建索引的时间根项目的文件多少成正比。 IntelliJ IDEA的缓存和索引主要是用来加快文件查询&#xff0c;从而加快各种查找、代码提示等操作的速度。 某些特殊情况下&#xf…

SpringBoot项目如何引进MyBatis ?如何使用及操作数据库

一、创建SpringBoot项目&#xff0c;导入依赖 首先需要创建一个SpringBoot项目&#xff0c;然后再pom.xml文件中&#xff0c;右击选择”Generate“,依次进行下述操作&#xff1a;&#xff08;同样也可以在项目创建的时候选择MyBatis Framework 和 MySQL Driver&#xff09; 然…

GitLab添加TortoiseGIT生成SSH Key

文章目录 前言一、PuTTYgen二、GitLab 前言 GitLab是一个用于托管代码仓库和项目管理的Web平台&#xff0c;公司搭建自己的gitlab来管理代码&#xff0c;我们在clone代码的时候可以选择http协议&#xff0c;也可以选择ssh协议来拉取代码。 SSH (Secure Shell)是一种通过网络进…

算法工程师第十九天(理论基础 组合 组合总和III 电话号码的字母组合)

参考文献 代码随想录 一、组合 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ] 示例 2&#xff…

Windows本地启动Redis

找到本地redis目录 输入cmd,然后输入redis-server.exe redis.windows.conf&#xff0c;默认端口为6379 再新打开一个cmd&#xff0c;输入redis-cli.exe -p 6379 -a &#xff08;你在redis.windows.conf中设置的密码&#xff09;

Kubeflow 模型管理和训练优化的新工具

Kubeflow 显著简化了安全机器学习模型和大语言模型&#xff08;LLMs&#xff09;的开发、调优和管理。亮点包括&#xff1a; 模型注册表&#xff1a;集中管理机器学习模型、版本和制品。 LLMs 微调 API&#xff1a;简化使用自定义数据集微调 LLMs 的过程。 流水线&#xff1a…

如何在 SpringBoot 中优雅的做参数校验?

一、故事背景 关于参数合法性验证的重要性就不多说了&#xff0c;即使前端对参数做了基本验证&#xff0c;后端依然也需要进行验证&#xff0c;以防不合规的数据直接进入服务器&#xff0c;如果不对其进行拦截&#xff0c;严重的甚至会造成系统直接崩溃&#xff01; 本文结合…

7月22日JavaSE学习笔记

Collection接口&#xff0c;还有一个父级接口Iterable可迭代的 Collection继承树 Set 集合 Set的底层是用Map实现&#xff08;存储在key中&#xff0c;value中是空的Object对象&#xff09; 有序&#xff1a;取出的顺序和添加的顺序是一样的。 List是有序的&#xff0c;Set是…

Idea2024 创建Meaven项目没有src文件夹

1、直接创建 新建maven项目&#xff0c;发现没有src/main/java 直接新建文件夹&#xff1a;右击项目名->new->Directory 可以看到idea给出了快捷创建文件夹的选项&#xff0c;可以根据需要创建&#xff0c;这里点击src/main/java 回车&#xff0c;可以看到文件夹已经创建…

搭建本地私有知识问答系统:MaxKB + Ollama + Llama3 (wsl网络代理配置、MaxKB-API访问配置)

目录 搭建本地私有知识问答系统:MaxKB、Ollama 和 Llama3 实现指南引言MaxKB+Ollama+Llama 3 Start buildingMaxKB 简介:1.1、docker部署 MaxKB(方法一)1.1.1、启用wls或是开启Hyper使用 WSL 2 的优势1.1.2、安装docker1.1.3、docker部署 MaxKB (Max Knowledge Base)MaxKB …

解决Windows 11更新错误0x800f081f的详细指南

在尝试更新Windows 11时&#xff0c;用户可能会遇到各种错误代码&#xff0c;其中之一是0x800f081f。这个错误通常与Windows更新组件或系统文件的损坏有关。本文将提供解决这一特定错误的详细步骤&#xff0c;并解释可能的原因。 错误代码0x800f081f概述 错误代码0x800f081f指…