ArrayList的线程安全类CopyOnWriteArrayList

news2024/11/16 1:29:26

在这里插入图片描述

目录

    • 一、CopyOnWriteArrayList简介
    • 二、CopyOnWriteArrayList的优缺点
      • 1、优点
      • 2、缺点
    • 三、CopyOnWriteArrayList使用场景
      • 1、数据库缓存
      • 2、消息队列
      • 3、数据统计和分析
    • 四、使用CopyOnWriteArrayList时需要注意哪些问题?
      • 1、内存占用问题
      • 2、数据一致性问题
      • 3、线程安全
      • 4、不支持add()、set()、remove()方法
    • 五、下面通过一段代码测试一下CopyOnWriteArrayList的性能。

大家好,我是哪吒。

在上一节中提到了通过ConcurrentHashMap解决HashMap在高并发下数据错乱的问题。

这篇简单介绍一下ArrayList的线程安全类CopyOnWriteArrayList。

一、CopyOnWriteArrayList简介

CopyOnWriteArrayList是ArrayList的一个线程安全的变体。它是通过在对底层数组进行一次新的复制来实现所有可变操作(如add、set等)的。在遍历时,它不会对任何元素进行修改,因此绝对不会抛出ConcurrentModificationException的异常。这种数据结构适合用在“读多,写少”的并发应用中,因为在这种情况下,读操作远远大于写操作,所以使用这种数据结构可以提高并发性能。但是,如果存在大量写操作,使用这种数据结构可能会导致性能下降,因为每次写操作都需要对整个底层数组进行复制。

二、CopyOnWriteArrayList的优缺点

1、优点

  1. 线程安全:CopyOnWriteArrayList实现了线程安全,可以在多线程环境下正常使用,避免了线程竞争和锁的问题;
  2. 并发读取性能好:由于CopyOnWriteArrayList在读取数据时不需要进行任何操作,所以在并发读取时效率很高;
  3. 可用于读多写少的场景:CopyOnWriteArrayList适合用在读多写少的场景中,可以大大提高并发性能。

2、缺点

  1. 内存占用高:由于CopyOnWriteArrayList每次写操作都需要复制整个底层数组,因此会导致内存占用较高。
  2. 写操作的延迟高:由于需要进行数据复制,所以CopyOnWriteArrayList的写操作延迟较高。
  3. 不适用于迭代器的弱一致性需求:CopyOnWriteArrayList的迭代器只能保证最终一致性,不能保证强一致性。因此,如果需要保证迭代器的一致性需求,建议使用其他数据结构。
  4. 不支持add()、set()、remove()方法:CopyOnWriteArrayList的迭代器实现了ListIterator接口,但是add()、set()、remove()方法都直接抛出了UnsupportedOperationException异常。

三、CopyOnWriteArrayList使用场景

CopyOnWriteArrayList主要适用于读多写少的并发场景,可以在多线程环境下提供高效的读取操作,同时保证线程安全。它适合用于缓存、读操作频繁而写操作较少的场景,例如:

1、数据库缓存

将CopyOnWriteArrayList作为数据库查询结果的缓存,可以避免在多线程环境下频繁地访问数据库,提高查询效率。

2、消息队列

在消息队列中,通常会有大量的读取操作和较少的写入操作,使用CopyOnWriteArrayList可以保证读取操作的并发性和效率。

3、数据统计和分析

在数据统计和分析过程中,通常会有大量的读取操作和较少的写入操作,使用CopyOnWriteArrayList可以提高读取操作的并发性和效率。

需要注意的是,由于CopyOnWriteArrayList的写操作会进行数据复制,可能会导致内存占用较高,因此不适合数据量过大的场景。此外,由于CopyOnWriteArrayList不支持add()、set()、remove()方法,因此也不适合需要频繁进行插入、更新、删除操作的场景。

四、使用CopyOnWriteArrayList时需要注意哪些问题?

1、内存占用问题

由于CopyOnWriteArrayList的写时复制机制,当进行写操作时,内存中会同时驻扎两个对象的内存,旧的对象和新写入的对象。在复制时只复制容器里的引用,在写时才会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存。

2、数据一致性问题

CopyOnWriteArrayList只能保证数据的最终一致性,不能保证数据的实时一致性。因为复制和操作元素需要一些时间,所以会有延迟。如果希望写入的数据马上能读到,要求数据强一致性的话,建议不要使用CopyOnWriteArrayList。

3、线程安全

CopyOnWriteArrayList是写同步,读非同步的。多个线程对CopyOnWriteArrayList进行写操作是线程安全的,但是在读操作时是非线程安全的。如果在for循环中使用下标的方式去读取数据,可能会报错ArrayIndexOutOfBoundsException。

4、不支持add()、set()、remove()方法

CopyOnWriteArrayList的迭代器实现了ListIterator接口,但是add()、set()、remove()方法都直接抛出了UnsupportedOperationException异常,所以应该避免使用迭代器的这几个方法。

五、下面通过一段代码测试一下CopyOnWriteArrayList的性能。

CopyOnWriteArrayList适合读多写少的并发场景,可以提供高效的读取操作和线程安全保障,但需要注意写操作开销较大和内存占用较高的问题。

public static void testWrite() {
    List<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
    List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());

    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    IntStream.rangeClosed(1, 100000).parallel().forEach(x -> copyOnWriteArrayList.add(UUID.randomUUID().toString()));
    stopWatch.stop();
    System.out.println("CopyOnWriteArrayList 写方法耗时:"+stopWatch.getTotalTimeSeconds());
    StopWatch stopWatch2 = new StopWatch();
    stopWatch2.start();
    IntStream.rangeClosed(1, 100000).parallel().forEach(x -> synchronizedList.add(UUID.randomUUID().toString()));
    stopWatch2.stop();

    System.out.println("List 写方法耗时:"+stopWatch2.getTotalTimeSeconds());
}
public static void testRead() {
    List<Integer> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
    List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
    copyOnWriteArrayList.addAll(IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList()));
    synchronizedList.addAll(IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList()));
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    IntStream.range(1, 1000000).parallel().forEach(x -> copyOnWriteArrayList.get(x));
    stopWatch.stop();
    System.out.println("copyOnWriteArrayList 读方法耗时:"+stopWatch.getTotalTimeSeconds());

    StopWatch stopWatch2 = new StopWatch();
    stopWatch2.start();
    IntStream.range(1, 1000000).parallel().forEach(x -> synchronizedList.get(x));
    stopWatch2.stop();
    System.out.println("List 读方法耗时:"+stopWatch2.getTotalTimeSeconds());
}

在这里插入图片描述

在高并发场景下,CopyOnWriteArrayList 写的速度比ArrayList加锁的方式足足慢了100倍,读的效率相差无几。印证了上面的结论,CopyOnWriteArrayList 适用于**“读多,写少”**的并发应用中。

上一篇:一个关于 i++ 和 ++i 的面试题打趴了所有人

🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师

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

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

相关文章

Milvus 入门教程

文章目录 下载docker-compose配置文件安装 docker安装docker-compose直接下载release版本手动安装使用pip 命令自动安装 通过 docker-compose 启动容器连接 Milvus停止 milvus删除milvus的数据 下载docker-compose配置文件 先安装wget命令 yum install wget下载配置文件&…

C++之C++11字符串字面量后缀总结(二百四十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

如何高效自学(黑客技术)方法——网络安全

如果你想自学网络安全&#xff0c;首先你必须了解什么是网络安全&#xff01;&#xff0c;什么是黑客&#xff01;&#xff01; 1.无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面性&#xff0c;例如 Web 安全技术&#xff0c;既有 Web 渗透2.也有 Web 防…

2023版 STM32实战12 IIC总线读写AT24C02

IIC简述 一个多主从的串行总线&#xff0c;又叫I2C&#xff0c;是由飞利浦公司发明的通讯总线 IIC特点 -1- 串行(逐bit传输) -2- 同步(共用时钟线) -3- 半双工(收发不同进行) -4- 总线上的任何设备都可以是主机 开发使用习惯和理解 -1- 通过地址寻址 -2- 数据线的…

Linux权限及Xshell运行原理

目录 1.Linux中的用户 1.1 用户分类 1.2 用户切换 2.权限的概念 2.1 权限概念以及表示 2.2 文件属性以及类型 2.2.1 文件属性 2.2.2 文件类型 2.3 Linux下的角色 3.权限的修改 3.1 chmod 3.2 chown 3.3 chgrp 4.目录权限 5.权限掩码 5.1 默认权限 5.2 起始权限…

省时省力!掌握简单快捷的关机命令,轻松实现电脑的自由开关机

本文介绍了为电脑设置特定的自动关机时间的四种方法。我们还包括如何停止计划关机的信息。 如何用命令提示符安排计算机关机 按照以下步骤使用命令提示符进行一次性关闭。 1、在Windows搜索框中,键入CMD。 2、点击Enter。 3、在命令提示符窗口中,键入shutdown -s -t 和所…

matlab simulink 四旋翼跟拍无人机仿真

1、内容简介 略 7-可以交流、咨询、答疑 2、内容说明 四旋翼跟拍无人机仿真 四旋翼、无人机 需求分析 背景介绍 无人飞行机器人&#xff0c;是无人驾驶且具有一定智能的空中飞行器。这是一种融合了计算机技术、人工智能技术、传感器技术、自动控制技术、新型材料技术、导航…

基于机器视觉的车道线检测 计算机竞赛

文章目录 1 前言2 先上成果3 车道线4 问题抽象(建立模型)5 帧掩码(Frame Mask)6 车道检测的图像预处理7 图像阈值化8 霍夫线变换9 实现车道检测9.1 帧掩码创建9.2 图像预处理9.2.1 图像阈值化9.2.2 霍夫线变换 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分…

HCL模拟器选路实验案例

此选路题目选自职业院校技能竞赛中的一道题比较考验思路&#xff0c;适合于参加新华三杯大赛以及网络专业的同学&#xff0c;当做练习题目进行解题​​​​​​​ 题目 1.S1、S2、R1、R2运行ospf进程100&#xff0c;区域0&#xff0c;R1、R2、R3、R4、R5运行ospf进程200&#…

Linux进阶之旅:从零开始,探索基本指令的神秘力量!

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是尘缘&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f449;点击这里&#xff0c;就可以查看我的主页啦&#xff01;&#x1f447;&#x…

【java学习—九】工厂方法FactoryMethod(6)

文章目录 1. 概念2. 实际的应用 1. 概念 FactoryMethod 模式是设计模式中应用最为广泛的模式&#xff0c;在面向对象的编程中&#xff0c;对象的创建工作非常简单&#xff0c;对象的创建时机却很重要。 FactoryMethod 解决的就是这个问题&#xff0c;它通过面向对象的手法&…

RabbitMQ初入门

1、RabbitMQ是什么 RabbitMQ是“实现了高级消息队列协议&#xff08;AMQP&#xff09;的开源消息代理软件&#xff08;亦称面向消息的中间件&#xff09;。RabbitMQ服务器是用Erlang语言编写的&#xff0c;而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均…

23 行为型模式-迭代器模式

1 迭代器模式介绍 迭代器模式是我们学习一个设计时很少用到的、但编码实现时却经常使用到的行为型设计模式。在绝大多数编程语言中&#xff0c;迭代器已经成为一个基础的类库&#xff0c;直接用来遍历集合对象。在平时开发中&#xff0c;我们更多的是直接使用它&#xff0c;很…

C++学习 day--21 地震监测系统实现、内存泄漏检测工具

1、项目需求 地震监测系统主要是利用地震检波器收集到的地壳运动信息&#xff0c;从而预测和确定地震的震中以 及强度。 预测方法 地震检波器每隔固定的时间间隔采样一次预测地震的能量数据&#xff0c;并保存到文件中&#xff0c;地震监测 系统会从文件中读取相应的能量数据&…

第四章 文件管理 十二、虚拟文件系统

目录 一、虚拟文件系统图 二、虚拟文件系统的特点 三、存在的问题 四、文件系统挂载 一、虚拟文件系统图 二、虚拟文件系统的特点 1、向上层用户进程提供统一标准的系统调用接口&#xff0c;屏蔽底层具体文件系统的实现差异。 2、VFS要求下层的文件系统必须实现某些规定的…

Map集合 遍历:lambda方式

package day01;import java.util.*;public class Mapday1 {public static void main(String[] args) {/* HashMap 无序 不重复&#xff0c;会覆盖前面 无索引*/System.out.println("--------------------");Map<String, Integer> map new HashMap<>();m…

【DBeaver】建立连接报驱动问题

事件 在DBeaver中建立pgsqlite连接&#xff0c;测试连接时&#xff0c;报 can’t load driver class ‘org.postgresql.Driver’ 问题原因 pgsqlite数据库驱动与DBeaver版本不匹配 pg解决办法 在https://jdbc.postgresql.org/download/中下载最新版本的驱动&#xff0c;然…

【Java基础】反射机制与动态代理机制

反射机制与动态代理机制 文章目录 反射机制与动态代理机制1. 反射(Reflection)的概念1.1 反射概述1.2 反射的优缺点 2. 理解Class类并获取Class实例2.1 Class2.2 Class类的常用方法 3. 反射的基本应用3.1 应用1&#xff1a;创建运行时类的对象3.2 应用2&#xff1a;调用运行时类…

什么是恶意代码?

前言&#xff1a;本文旨在分享交流技术&#xff0c;在这里对恶意代码进行全面的介绍和讲解 目录 一.什么是恶意代码 二.恶意代码的发展史 三.恶意代码的相关定义 四.恶意代码攻击机制 PE病毒 PE文件的格式 脚本病毒 脚本文件隐藏方法 宏病毒 浏览器恶意代码 U盘病毒 …

Go基础——数组、切片、集合

目录 1、数组2、切片3、集合4、范围&#xff08;range&#xff09; 1、数组 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列&#xff0c;这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。 Go 语言数组声明需要指定元素类型及元素个数&#xff0c;与…