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

JAVA-CopyOnWrite并发集合

文章目录

    • JAVA并发集合
    • 1_实现原理
    • 2_什么是CopyOnWrite?
    • 3_CopyOnWriteArrayList的原理
    • 4_CopyOnWriteArraySet
    • 5_使用场景
    • 6_总结

JAVA并发集合

从Java5开始,Java在java.util.concurrent包下提供了大量支持高效并发访问的集合类,它们既能包装良好的访问性能,有能包装线程安全。这些集合类可以分为两部分,它们的特征如下:

  • 以Concurrent开头的集合类:
    以Concurrent开头的集合类代表了支持并发访问的集合,它们可以支持多个线程并发写入访问,
    这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。以Concurrent开头的集合类
    用了更复杂的算法来保证永远不会锁住整个集合
    ,因此在并发写入时有较好的性能。
  • 以CopyOnWrite开头的集合类:
    以CopyOnWrite开头的集合类采用复制底层数组的方式来实现写操作。当线程对此类集合执行读
    取操作时,线程将会直接读取集合本身,无须加锁与阻塞。当线程对此类集合执行写入操作时,集
    合会在底层复制一份新的数组,接下来对新的数组执行写入操作。由于对集合的写入操作都是对数
    组的副本执行操作
    ,因此它是线程安全的。
  • 扩展阅读
    java.util.concurrent包下线程安全的集合类的体系结构:
    在这里插入图片描述

本文将主要讲解以CopyOnWrite开头的集合类:

1_实现原理

在Java中,CopyOnWrite系列的集合(如CopyOnWriteArrayListCopyOnWriteArraySet)是线程安全的集合类,适用于读操作频繁且写操作相对较少的场景。它们通过一种名为 “写时复制”(Copy-On-Write,简称COW)的策略来实现线程安全。

2_什么是CopyOnWrite?

概括为"写时复制",通俗的讲是写数据的时候弄出一个新的数组,然后讲旧的数据拷贝过去,更新后再将引用指向新数组。这样在添加删除元素时就不会影响旧数组的读取了,确保高并发时读的效率,但是存在延时。

下面以CopyOnWriteArrayListCopyOnWriteArraySet为例对CopyOnWrite系列的集合进一步讲解。

3_CopyOnWriteArrayList的原理

CopyOnWriteArrayList是一个线程安全的可变数组实现,内部通过复制底层数组来处理并发写操作。其主要特性是:

  • 读操作不需要锁:因为读操作不会修改数组,因此可以并发进行。
  • 写操作通过复制实现:每次写操作(如添加、删除、更新)都会创建底层数组的一个新副本,修改副本后再将其设置为新的底层数组。

内部实现机制:

以下是CopyOnWriteArrayList的核心实现机制:

  • 底层数据结构
    CopyOnWriteArrayList内部使用一个volatile修饰的数组来存储元素,确保多线程环境下对数组的可见性。

    private transient volatile Object[] array;
    
  • 读操作

    读操作直接访问底层数组,无需加锁。

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

    写操作在进行修改时,会首先复制底层数组,然后在新数组上进行修改,最后将新数组设置为底层数组。

    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();                        // 释放锁}
    }final void setArray(Object[] a) {array = a;
    }
    

在这个示例中,add方法首先获取锁以确保写操作的线程安全。然后,它复制现有的数组,增加一个新元素,并将新数组设置为底层数组。

添加操作会复制新的数组并将原元素长度加一,那么移除一个元素呢?

  • 移除操作:
    public boolean remove(Object o) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;// 寻找要移除的元素的位置int i = 0;for (; i < len; i++) {if (o.equals(elements[i])) {break;}}// 如果没有找到元素,直接返回falseif (i == len) {return false;}// 创建新数组,长度比当前数组少一个Object[] newElements = new Object[len - 1];// 复制前面的元素System.arraycopy(elements, 0, newElements, 0, i);// 复制后面的元素System.arraycopy(elements, i + 1, newElements, i, len - i - 1);// 设置新数组setArray(newElements);return true;} finally {lock.unlock();}
    }

CopyOnWriteArrayList中,移除元素的操作也是通过复制数组并在新数组上进行操作来实现的。虽然每次移除操作都会创建一个新数组,存在一定的性能开销,但这种设计能够确保线程安全,适合读多写少的场景。

既然remove、add已经了解了,那么 set 也就不难猜测了,public E set(int index, E element)操作时也是拷贝原数组然后进行操作,只不过长度相对原数组既没有增加也没有减少。

4_CopyOnWriteArraySet

CopyOnWriteArraySet是基于CopyOnWriteArrayList实现的线程安全的集合。它利用CopyOnWriteArrayList来存储元素,并确保集合中的元素不重复。

5_使用场景

CopyOnWrite集合适用于以下场景:

  1. 读多写少:由于每次写操作都会复制整个数组,因此写操作的开销较大。适用于读操作频繁、写操作较少的场景。
  2. 遍历操作:在遍历过程中,不会受到并发修改的影响,因为任何写操作都会创建一个新的数组副本,不会修改正在遍历的数组。

优缺点

优点

  • 线程安全:通过写时复制机制实现线程安全,读操作无锁,性能高。
  • 适用于读多写少的场景:在读操作远多于写操作的情况下,性能表现优秀。
  • 不需要手动同步:用户无需手动添加同步代码,简化了并发编程。

缺点

  • 内存开销大:每次写操作都会创建数组副本,占用额外的内存。
  • 写操作性能差:写操作需要复制数组,性能较低。

示例代码

以下是CopyOnWriteArrayList的简单示例代码:

import java.util.concurrent.CopyOnWriteArrayList;public class CopyOnWriteExample {public static void main(String[] args) {CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();// 添加元素list.add(1);list.add(2);list.add(3);// 读取元素System.out.println("Element at index 0: " + list.get(0));// 遍历元素for (Integer element : list) {System.out.println("Element: " + element);}// 删除元素list.remove(Integer.valueOf(2));System.out.println("After removal: " + list);}
}

6_总结

CopyOnWrite系列集合通过写时复制机制实现线程安全,适用于读操作频繁且写操作较少的场景。虽然写操作的开销较大,但在读操作占多数的应用中,CopyOnWrite集合可以提供高效且线程安全的性能。

CopyOnWriteArrayList中,移除元素的操作与添加元素类似,通过复制数组并在新数组上进行操作来实现线程安全。移除元素时,需要创建一个新的数组,长度比当前数组少一个,然后复制所有不需要移除的元素到新数组中。

相关文章:

  • Mybatis面试系列六
  • 博科SAN交换机初始化和Zone创建
  • 分布式管理
  • visual studio 2022使用全版本平台工具集
  • 2024福建等保测评公司有哪些?分别叫做什么名字?
  • 826. 安排工作以达到最大收益
  • Android 13 高通设备热点低功耗模式(2)
  • 2021年9月电子学会青少年软件编程 中小学生Python编程等级考试三级真题解析(判断题)
  • openssl工具国际/国密签名命令行流程
  • Web前端与其他前端:深度对比与差异性剖析
  • AlmaLinux 8.10 x86_64 OVF (sysin) - VMware 虚拟机模板
  • Python酷库之旅-比翼双飞情侣库(08)
  • flutter报错You are currently using Java 1.8
  • 每日5题Day24 - LeetCode 116 - 120
  • Ollama在MacOS、Linux本地部署千问大模型及实现WEB UI访问
  • 3.7、@ResponseBody 和 @RestController
  • Gradle 5.0 正式版发布
  • js 实现textarea输入字数提示
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • PaddlePaddle-GitHub的正确打开姿势
  • Python打包系统简单入门
  • Python学习之路16-使用API
  • Ruby 2.x 源代码分析:扩展 概述
  • scala基础语法(二)
  • Spring-boot 启动时碰到的错误
  • ⭐ Unity + OpenCV 实现实时图像识别与叠加效果
  • webpack4 一点通
  • 包装类对象
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 原生Ajax
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • - 转 Ext2.0 form使用实例
  • MPAndroidChart 教程:Y轴 YAxis
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​数据结构之初始二叉树(3)
  • # 计算机视觉入门
  • #Z2294. 打印树的直径
  • #微信小程序(布局、渲染层基础知识)
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (175)FPGA门控时钟技术
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (ZT)一个美国文科博士的YardLife
  • (层次遍历)104. 二叉树的最大深度
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (每日一问)计算机网络:浏览器输入一个地址到跳出网页这个过程中发生了哪些事情?(废话少说版)
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (一)为什么要选择C++
  • (转)重识new
  • ***测试-HTTP方法
  • .Net Core与存储过程(一)