Java并发编程与技术内幕:CopyOnWriteArrayList、CopyOnWriteArraySet源码解析

本文主要是介绍Java并发编程与技术内幕:CopyOnWriteArrayList、CopyOnWriteArraySet源码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、CopyOnWriteArrayList源码分析

CopyOnWriteArrayList在java的并发场景中用得其实并不是非常多,因为它并不能完全保证读取数据的正确性。其主要有以下的一些特点:
1、适合场景读多写少
2、不能保证读取数据一定是正确 的,因为get时是不加锁的
3、add、remove会加锁再来操作

下面来看看源码:
包含的数据结构

 

 
  1. public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

  2. /** 重入锁*/

  3. transient final ReentrantLock lock = new ReentrantLock();

  4. /** 真正存放数据的结构*/

  5. private volatile transient Object[] array;


其实它的数据结构非常简单,就是一个数组和一个重入锁。

 

 

 

构造函数有三个:

 

 
  1. /**

  2. * 构造函数,创建一个空数组

  3. */

  4. public CopyOnWriteArrayList() {

  5. setArray(new Object[0]);

  6. }

  7.  
  8. /**

  9. * 构造函数,从集合中来初始化列表

  10. */

  11. public CopyOnWriteArrayList(Collection<? extends E> c) {

  12. Object[] elements = c.toArray();

  13. if (elements.getClass() != Object[].class)

  14. elements = Arrays.copyOf(elements, elements.length, Object[].class);//将elements数组内容都转换成Object类型

  15. setArray(elements);

  16. }

  17.  
  18. /**

  19. * 构造函数,从数组来初始化列表

  20. */

  21. public CopyOnWriteArrayList(E[] toCopyIn) {

  22. setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));//注意,都向上转型为Object类型

  23. }

 

主要方法:

1、add时加锁
 

 
  1. public boolean add(E e) {

  2. final ReentrantLock lock = this.lock;

  3. lock.lock();//取得锁

  4. try {

  5. Object[] elements = getArray();

  6. int len = elements.length;

  7. Object[] newElements = Arrays.copyOf(elements, len + 1);//将当前elements复制到一个长度加1的数组中

  8. newElements[len] = e;//设置最后一个元素为e

  9. setArray(newElements);//设置当前元素数组,在这里如果执行get时就有可能会出错

  10. return true;

  11. } finally {

  12. lock.unlock();//释放锁

  13. }

这里在注意这个方法:

 

addIfAbsent这个方法CopyOnWriteArraySet会用到(其实CopyOnWriteArraySet就是封装了一个CopyOnWriteArrayList,下文会说到)

 

 
  1. //如果已存在就不放入

  2. public boolean addIfAbsent(E e) {

  3. final ReentrantLock lock = this.lock;

  4. lock.lock();//取得锁

  5. try {

  6. Object[] elements = getArray();

  7. int len = elements.length;

  8. Object[] newElements = new Object[len + 1];

  9. for (int i = 0; i < len; ++i) { //一个一个元素取出来进行对比

  10. if (eq(e, elements[i]))

  11. return false; // 跳出,list中已存在此元素

  12. else

  13. newElements[i] = elements[i]; //一个一个拷贝到新数组

  14. }

  15. newElements[len] = e;

  16. setArray(newElements);//设置新数组

  17. return true;//加入成功

  18. } finally {

  19. lock.unlock();//释放锁

  20. }

  21. }

 

 

 

 

 

 

 

2、删除时在加锁
删除方法一

 
  1. public E remove(int index) {

  2. final ReentrantLock lock = this.lock;

  3. lock.lock();//取得锁

  4. try {

  5. Object[] elements = getArray();

  6. int len = elements.length;

  7. Object oldValue = elements[index];//查到要删除的元素

  8. int numMoved = len - index - 1;

  9. if (numMoved == 0)//删除的刚好是最后一个元素

  10. setArray(Arrays.copyOf(elements, len - 1));//直接一次拷贝就可以完成

  11. else {

  12. Object[] newElements = new Object[len - 1];

  13. System.arraycopy(elements, 0, newElements, 0, index);//前半部分拷贝

  14. System.arraycopy(elements, index + 1, newElements, index,numMoved);//后半部分拷贝

  15. setArray(newElements);//重新设置数组

  16. }

  17. return (E)oldValue;//返回删除的元素

  18. } finally {

  19. lock.unlock();//释放锁

  20. }

  21. }

删除方法二

 

 

 
  1. public boolean remove(Object o) {

  2. final ReentrantLock lock = this.lock;

  3. lock.lock();

  4. try {

  5. Object[] elements = getArray();

  6. int len = elements.length;

  7. if (len != 0) {

  8. int newlen = len - 1;

  9. Object[] newElements = new Object[newlen];//设置新数组大小

  10.  
  11. for (int i = 0; i < newlen; ++i) {

  12. if (eq(o, elements[i])) {

  13. // found one; copy remaining and exit

  14. for (int k = i + 1; k < len; ++k)//找到一个,那么把后面的依次循环拷贝到新数组就完成此次操作

  15. newElements[k-1] = elements[k];

  16. setArray(newElements);//设置新数组

  17. return true;

  18. } else

  19. newElements[i] = elements[i];//没有找到相等的元素

  20. }

  21.  
  22. if (eq(o, elements[newlen])) {//判断是后一个是否相等

  23. setArray(newElements);//设置新数组

  24. return true;

  25. }

  26. }

  27. return false;//如果到这里,表明不包含这个元素,原数组的内容不做改变

  28. } finally {

  29. lock.unlock();

  30. }

  31. }

 


3、get时不加锁

 
  1. public E get(int index) {

  2. return (E)(getArray()[index]);//直接取得数组对应的索引上的数据就返回了,这里在取得有可能原数组在取完成后就发生改变

  3. }

  4.  
  5. final Object[] getArray() {

  6. return array;

  7. }


4、contains时不加锁

 

 

 

  1. public boolean contains(Object o) {

  2. Object[] elements = getArray();

  3. return indexOf(o, elements, 0, elements.length) >= 0;

  4. }

  5.  
  6. private static int indexOf(Object o, Object[] elements,

  7. int index, int fence) {

  8. if (o == null) {

  9. for (int i = index; i < fence; i++)

  10. if (elements[i] == null)

  11. return i;

  12. } else {

  13. for (int i = index; i < fence; i++)

  14. if (o.equals(elements[i]))

  15. return i;

  16. }

  17. return -1;

  18. }

 

二、CopyOnWriteArraySet源码分析

 

 

CopyOnWriteArraySet的原理场景和CopyOnWriteArrayList一样,只不过是不能有重复的元素放入,它里面包含一个CopyOnWriteArrayList,真正调用的方法都 是CopyOnWriteArrayList的方法

数据结构:

 

 
  1. public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable {

  2. private static final long serialVersionUID = 5457747651344034263L;

  3.  
  4. private final CopyOnWriteArrayList<E> al; //只包含有一个CopyOnWriteArrayList

  5. 。。

  6. }

可以看到就只有一个CopyOnWriteArrayList一个成员变量

 

其主要方法:

1、add方法

 
  1. public boolean add(E e) {

  2. return al.addIfAbsent(e); //调用CopyOnWriteArrayList的addIfAbsent方法,如果数据重复返回false,不加入

  3. }


2、remove方法

 

 

 

  1. public boolean remove(Object o) {

  2. return al.remove(o); //调用CopyOnWriteArrayList 的remove方法

  3. }


3、CopyOnWriteArraySet没有get方法,只能通过Iterator来依次取

 

 

 

  1. public Iterator<E> iterator() {

  2. return al.iterator();//调用CopyOnWriteArrayList的iterator()方法

  3. }

而CopyOnWriteArrayList的iterator()方法如下:

 

 

 

  1. public Iterator<E> iterator() {

  2. return new COWIterator<E>(getArray(), 0);

  3. }

其中COWIterator是CopyOnWriteArrayList的一个内部类,其主要数据结构如下:

 

 

 

  1. private static class COWIterator<E> implements ListIterator<E> {

  2. //数组

  3. private final Object[] snapshot;

  4. //当前指针指向数组地址

  5. private int cursor;

  6.  
  7. private COWIterator(Object[] elements, int initialCursor) {

  8. cursor = initialCursor;

  9. snapshot = elements;

  10. }

  11. 。。。。。

  12. }

 

 

 

这篇关于Java并发编程与技术内幕:CopyOnWriteArrayList、CopyOnWriteArraySet源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

java中新生代和老生代的关系说明

《java中新生代和老生代的关系说明》:本文主要介绍java中新生代和老生代的关系说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、内存区域划分新生代老年代二、对象生命周期与晋升流程三、新生代与老年代的协作机制1. 跨代引用处理2. 动态年龄判定3. 空间分

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

深度解析Java DTO(最新推荐)

《深度解析JavaDTO(最新推荐)》DTO(DataTransferObject)是一种用于在不同层(如Controller层、Service层)之间传输数据的对象设计模式,其核心目的是封装数据,... 目录一、什么是DTO?DTO的核心特点:二、为什么需要DTO?(对比Entity)三、实际应用场景解析

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

深度解析Java项目中包和包之间的联系

《深度解析Java项目中包和包之间的联系》文章浏览阅读850次,点赞13次,收藏8次。本文详细介绍了Java分层架构中的几个关键包:DTO、Controller、Service和Mapper。_jav... 目录前言一、各大包1.DTO1.1、DTO的核心用途1.2. DTO与实体类(Entity)的区别1