Java并发-CopyOnWriteArrayList

2024-01-19 22:32

本文主要是介绍Java并发-CopyOnWriteArrayList,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简述

ArrayList不是一个线程安全的类,在我们开发中要让List线程安全可能会用到Vector,而Vector是直接在方法上用synchronized关键字实现线程同步的的,性能有比较大的问题,并发包中提供了CopyOnWriteArrayList,CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略。性能会更好。

public class CopyOnWriteArrayList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable {//独占锁,后面发生对list的操作都要用到final transient ReentrantLock lock = new ReentrantLock();//存放具体的元素private transient volatile Object[] array;//...}

lock和array是CopyOnWriteArrayList中两个比较重要的属性。

CopyOnWriteArrayList涉及修改的方法

CopyOnWriteArrayList中涉及到修改的方法都是要先拷贝一个数组,在拷贝数组上进行操作,之后再用拷贝的数组替换原本的数组。

// 用于修改数组中的元素
public E set(int index, E element) {final ReentrantLock lock = this.lock;// 拿到独占锁lock.lock();try {// 拿到存放元素的数组Object[] elements = getArray();// 元素的旧值E oldValue = get(elements, index);// 元素不相等if (oldValue != element) {int len = elements.length;// 拷贝一个新数组Object[] newElements = Arrays.copyOf(elements, len);// 把新数组中的对应的值进行更新newElements[index] = element;// 用新数组替换旧的数组setArray(newElements);} else {//要更新的值如果和旧值是一样的,其实我们可以不用进行操作,这里还是重新设置了一下Array,是为了保证volatile语义setArray(elements);}return oldValue;} finally {// 释放锁lock.unlock();}
}
// 在数组中添加元素
public boolean add(E e) {final ReentrantLock lock = this.lock;// 拿到独占锁lock.lock();try {// 拿到存放元素的数组Object[] elements = getArray();int len = elements.length;// 拷贝出一个比元素组长度多1的新数组Object[] newElements = Arrays.copyOf(elements, len + 1);// 新数组中插入元素newElements[len] = e;// 用新数组替换旧的数组setArray(newElements);return true;} finally {// 释放锁lock.unlock();}
}
// 数组中删除元素
public E remove(int index) {final ReentrantLock lock = this.lock;// 拿到独占锁lock.lock();try {// 拿到存放元素的数组Object[] elements = getArray();int len = elements.length;E oldValue = get(elements, index);int numMoved = len - index - 1;if (numMoved == 0)setArray(Arrays.copyOf(elements, len - 1));else {Object[] newElements = new Object[len - 1];// 要删除元素两边的其他元素合并System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index + 1, newElements, index,numMoved);// 用新数组替换旧的数组setArray(newElements);}return oldValue;} finally {// 释放锁lock.unlock();}
}

每一个涉及到变动数组的操作都要先获取锁,这样是为了保证同一个时刻只有一个线程在修改数组。而每一次更新的错都是操作拷贝的数组,这样原本在就数组上进行的查询、迭代就可以不受影响,这就是写时复制。

获得元素

// 获取元素的入口
public E get(int index) {return get(getArray(), index);
}// 获得集体元素
private E get(Object[] a, int index) {return (E) a[index];
}//获得存放元素的数组
final Object[] getArray() {return array;
}

通过代码可以看到获取元素时分成两个步骤的:

  1. 获取数组
  2. 通过下标获取具体的元素

如果没有进行处理,那么这样的步骤在并发下就有可能出现问题,但是由于发生变动的操作都是同步的,并且时写时复制的,所以就算在第一步第二步直接发生了数据变化,查询也不会被影响到。(因为操作的是修改之前的数组,而变更操作更新的都是复制之后的新数组)

总结

CopyOnWriteArrayList使用写时复制的策略来保证list的一致性,而获取―修改—写入三步操作并不是原子性的,所以在增删改的过程中都使用了独占锁,来保证在某个时间只有一个线程能对list数组进行修改。另外在查询期间,其他线程对list的修改是不可见的,查询的数组是一个快照。

这篇关于Java并发-CopyOnWriteArrayList的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/623899

相关文章

Java实现按字节长度截取字符串

《Java实现按字节长度截取字符串》在Java中,由于字符串可能包含多字节字符,直接按字节长度截取可能会导致乱码或截取不准确的问题,下面我们就来看看几种按字节长度截取字符串的方法吧... 目录方法一:使用String的getBytes方法方法二:指定字符编码处理方法三:更精确的字符编码处理使用示例注意事项方

Spring三级缓存解决循环依赖的解析过程

《Spring三级缓存解决循环依赖的解析过程》:本文主要介绍Spring三级缓存解决循环依赖的解析过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、循环依赖场景二、三级缓存定义三、解决流程(以ServiceA和ServiceB为例)四、关键机制详解五、设计约

spring IOC的理解之原理和实现过程

《springIOC的理解之原理和实现过程》:本文主要介绍springIOC的理解之原理和实现过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、IoC 核心概念二、核心原理1. 容器架构2. 核心组件3. 工作流程三、关键实现机制1. Bean生命周期2.

解决tomcat启动时报Junit相关错误java.lang.ClassNotFoundException: org.junit.Test问题

《解决tomcat启动时报Junit相关错误java.lang.ClassNotFoundException:org.junit.Test问题》:本文主要介绍解决tomcat启动时报Junit相... 目录tomcat启动时报Junit相关错误Java.lang.ClassNotFoundException

Gradle下如何搭建SpringCloud分布式环境

《Gradle下如何搭建SpringCloud分布式环境》:本文主要介绍Gradle下如何搭建SpringCloud分布式环境问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Gradle下搭建SpringCloud分布式环境1.idea配置好gradle2.创建一个空的gr

JVM垃圾回收机制之GC解读

《JVM垃圾回收机制之GC解读》:本文主要介绍JVM垃圾回收机制之GC,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、死亡对象的判断算法1.1 引用计数算法1.2 可达性分析算法二、垃圾回收算法2.1 标记-清除算法2.2 复制算法2.3 标记-整理算法2.4

springboot集成Lucene的详细指南

《springboot集成Lucene的详细指南》这篇文章主要为大家详细介绍了springboot集成Lucene的详细指南,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起... 目录添加依赖创建配置类创建实体类创建索引服务类创建搜索服务类创建控制器类使用示例以下是 Spring

Java调用Python的四种方法小结

《Java调用Python的四种方法小结》在现代开发中,结合不同编程语言的优势往往能达到事半功倍的效果,本文将详细介绍四种在Java中调用Python的方法,并推荐一种最常用且实用的方法,希望对大家有... 目录一、在Java类中直接执行python语句二、在Java中直接调用Python脚本三、使用Run

Java根据IP地址实现归属地获取

《Java根据IP地址实现归属地获取》Ip2region是一个离线IP地址定位库和IP定位数据管理框架,这篇文章主要为大家详细介绍了Java如何使用Ip2region实现根据IP地址获取归属地,感兴趣... 目录一、使用Ip2region离线获取1、Ip2region简介2、导包3、下编程载xdb文件4、J

浅析如何使用xstream实现javaBean与xml互转

《浅析如何使用xstream实现javaBean与xml互转》XStream是一个用于将Java对象与XML之间进行转换的库,它非常简单易用,下面将详细介绍如何使用XStream实现JavaBean与... 目录1. 引入依赖2. 定义 JavaBean3. JavaBean 转 XML4. XML 转 J