尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

从源码看 CopyOnWriteArrayList 的线程安全机制

从源码看 CopyOnWriteArrayList 的线程安全机制
📅 发布时间:2026/7/22 16:13:38

目录

一.CpoyOnWriteArrayList 原理简单概述

二. CopyOnWriteArrayList 源码分析

2.1 属性构造器解读

2.2 get 方法分析

2.3 add 方法分析

2.4 set 方法分析

2.5 remove 方法分析

三. 总结概括


使用过 ArrayList 集合的同学应该大致都知道,ArrayList 是一个非线程安全的集合;

同样,Java也为我们提供了线程安全的 List 集合,它就是我们本篇文章要说的 CopyOnWriteArrayList。

只是因为开发过程中通常会使用其他的的方式保证线程安全,所以它使用的频率没有 ArrayList 那么频繁。

一.CpoyOnWriteArrayList 原理简单概述

CopyOnWriteArrayList 的底层采用了加锁的方式保证线程安全,并且加的是 Lock 锁而不是 Sychonized 锁。

假如现在有两个线程,一个读线程A,一个写线程B,同时想要想数组中添加元素,读线A程就会读取当前内存中 CopyOnWriteArrayList 集合,写线程B则是会将内存中的 CopyOnWriteArrayList 集合对象复制一份新的,在新复制的集合中执行添加操作,添加操作完成之后再将新的集合赋值给原来老的集合,并且这个过程中写线程B会获取唯一的 Lock 锁,其它写线程会阻塞等待,实现读写分离。那么假如说有第三个写线程C也想要执行写数据操作,就需要等待写线程B操作完成之后释放 Lock 锁自己获取到 Lock 锁之后才能去执行写入操作。

二. CopyOnWriteArrayList 源码分析

2.1 属性构造器解读

下面是我粘贴的一部分属性,get,set方法,构造方法。

(1)可以看到在 CopyOnWriteArrayList 内部它定义了一个 Lock 锁对象;

(2)底层定义了一个名为 array 的对象数组;

(3)无参构造可以看出调用无参构造会将 array 数组对象的长度设置为 0,只有在进行存储元素的时候才回去扩容;

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array; /** * Gets the array. Non-private so as to also be accessible * from CopyOnWriteArraySet class. */ final Object[] getArray() { return array; } /** * Sets the array. */ final void setArray(Object[] a) { array = a; } /** * Creates an empty list. */ public CopyOnWriteArrayList() { setArray(new Object[0]); }

2.2 get 方法分析

下面是 CopyOnWriteArrayList 的 get 获取元素的方法,这里它 index 和数组的长度大小都没有做判断,所以很有可能会出现索引越界异常;

get 获取元素的方法很简单,没有出现加锁的行为

public E get(int index) { // 直接返回对象 index 位置的元素 return get(getArray(), index); }

2.3 add 方法分析

public void add(int index, E element) { // 获取 Lock 锁 final ReentrantLock lock = this.lock; // 调用方法上锁 lock.lock(); try { // 获取内存的数组对象并赋值为 elements Object[] elements = getArray(); // 定义一个变量 len 获取数组的长度 int len = elements.length; // 判断方法的参数 index 是否越界或合法 if (index > len || index < 0) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len); // 定义一个新数组对象 newElements Object[] newElements; // 定义一个变量 numMoved 接收数组长度 - index 的值 int numMoved = len - index; // 如果 numMoved 为0,则说明要把新添加的元素放在数组的最后 if (numMoved == 0) // 调用调用 copyOf 方法将原来数组中的数据全部复制到 newElements中, // 并在数组末尾添加上新的元素 newElements = Arrays.copyOf(elements, len + 1); else { // 如果 numMoved 不为0,则说明要将该元素添加在数组中间的某个位置 // 先将新数组的长度 + 1 newElements = new Object[len + 1]; // 将老数组 0~index 之间的数据全部复制到新数组中 System.arraycopy(elements, 0, newElements, 0, index); // 再将 index~数组最后的数据全部复制到新数组中 System.arraycopy(elements, index, newElements, index + 1, numMoved); } // 将要添加的元素 element 添加到新数组的 index 的位置 newElements[index] = element; // 将老数组地址值赋值给新数组对象 setArray(newElements); } finally { // 操作完毕,最后释放锁 lock.unlock(); } }

2.4 set 方法分析

public E set(int index, E element) { // 获取 Lock 锁 final ReentrantLock lock = this.lock; // 调用方法上锁 lock.lock(); try { // 获取内存的数组对象并赋值给一个新的数组对象 elements Object[] elements = getArray(); // 获取 index 处的元素 E oldValue = get(elements, index); // 判断 oldValue 和要插入的元素是否相等 if (oldValue != element) { // 获取数组的长度 int len = elements.length; // 将原本的数组数据复制到新数组 newElements 中 Object[] newElements = Arrays.copyOf(elements, len); // 将 element 放置到新数组的 index 处 newElements[index] = element; // 将新数组覆盖原来的数组 setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics // 进入 else ,说明要set的元素在数组中已经存在,直接返回原数组 setArray(elements); } // 返回位置 index 处的老的元素 return oldValue; } finally { // 操作完成,释放 lock 锁 lock.unlock(); } }

2.5 remove 方法分析

public E remove(int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); // 获取数组长度 int len = elements.length; // 获取 index 处的元素 E oldValue = get(elements, index); // 定义 numMoved 计算出要移动的元素的数量 int numMoved = len - index - 1; // 如果 numMoved 为0,说明要删除的元素恰好是数组的最后一个元素 if (numMoved == 0) // 覆盖原来的数组 setArray(Arrays.copyOf(elements, len - 1)); else // numMoved 不为0,则定义一个新数组,长度为原来的数组长度-1 Object[] newElements = new Object[len - 1]; // 将 0~index 处的元素复制到新数组中去 System.arraycopy(elements, 0, newElements, 0, index); // 将 index+1~数组最后的元素移动到新数组中 System.arraycopy(elements, index + 1, newElements, index, numMoved); // 覆盖原来的数组 setArray(newElements); } // 返回删除的元素值 return oldValue; } finally { // 操作完毕,释放 lock 锁 lock.unlock(); } }

三. 总结概括

经过上面对 add 添加方法,get 获取方法,set 修改方法,remove 删除方法的分析,其实也可以看出,相比于原来 ArrayList 集合的基础上添加了一把 lock 。

在做增,改,删三种操作的时候,搭配上 copy 复制数组的思想,就可以做到线程安全,这就是 CopyOnWriteArrayList 线程安全的核心设计思想,不算特别难理解。

相关新闻

  • 2026 网安学习资源大评测,拒绝无效资料堆砌
  • DFS序详解:原理、应用与实现
  • VirtualLab Fusion:为光线追迹生成光线

最新新闻

  • 运动耳机如何选出舒适好用的?实测十款热门耳机,揭晓综合实力
  • 服装质检的视觉盲区怎么检测?
  • Local Web API详解:通过HTTP请求控制Roblox Account Manager
  • 深入解析TI C2000 DSP的CLB_XBAR_REGS寄存器:信号路由配置实战
  • TMS320F2837xS ePWM/eCAP Driverlib函数与寄存器映射深度解析
  • Base64工具库:编码与解码工具类(239)

日新闻

  • AI云原生实战05-金融AI上云最难的不是技术,是“不出事“——TCE银行风控架构拆解
  • 2026年GEOSEO优化公司选型深度测评:五大硬核标准严选,这六家重塑搜索增长新格局 - 品牌前沿专家
  • **核验!2026年7月卡地亚香港**售后网点地址及服务电话公告 - 卡地亚服务中心

周新闻

  • SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径
  • 什么是PCTFE?医药高端包装的“防潮王牌“材料
  • 【JVM调优实战】16-可视化利器-JConsole-VisualVM-JMC

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号