热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

jdk11源码COW

解决了什么问题Athread-safevariantofArrayListinwhichallmutativeoperations(add,set,andsoon)引用CopyOn

解决了什么问题

A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on)

引用CopyOnWriteArrayList的doc就是解决了ArrayList并发场景下对容器的修改安全性问题

 


解决方案

涉及修改数组数据或者结构的操作引用副本

 


优点



  • 实现简单 空间换时间

  • 读多写少场景下几乎不存在因为锁导致的性能瓶颈(极端情况下只读不写则跟ArrayList是一样的)

 


弊端



  • 空间开销

  • 数据延迟带来的不一致

 


源码

public class CopyOnWriteArrayList implements List, RandomAccess, Cloneable, java.io.Serializable

实现跟ArrayList几乎一样,只关注List的几个常用api就行。

 


CopyOnWriteArrayList#get(int)

没有对入参的脚标显式代码校验,交给jdk底层数组索引IndexOutOfBoundsException

public E get(int index) {
return this.elementAt(this.getArray(), index); // 获取数组 根据脚标寻址
}
static E elementAt(Object[] a, int index) {
return (E) a[index];
}
final Object[] getArray() {
return this.array;
}

CopyOnWriteArrayList#set(int, E)

private transient volatile Object[] array; // volatile修饰 jvm语义保证了线程可见性
public E set(int index, E element) {
synchronized (lock) { // 管程锁保证线程安全
Object[] es = this.getArray(); // 数组
E oldValue = this.elementAt(es, index); // index脚标上的值
if (oldValue != element) { // 直接 if(oldValue==element) return oldValue;应该更高效
es = es.clone(); // 副本
es[index] = element; // 新值替旧值
}
// Ensure volatile write semantics even when oldvalue == element
this.setArray(es); // 将数组引用指向拷贝的副本
return oldValue;
}
}

CopyOnWriteArrayList#add(E)

public boolean add(E e) {
synchronized (lock) { // 管程锁
Object[] es = this.getArray(); // 数组
int len = es.length;
es = Arrays.copyOf(es, len + 1); // 拷贝数组
es[len] = e; // 添加元素到末尾脚标
setArray(es); // 数组引用指向副本
return true;
}
}

CopyOnWriteArrayList#add(int,E)

public void add(int index, E element) { // 源数组[...] 要在指定idx上增加一个元素e 从idx往后的元素依次后移 [...idx...]
synchronized (lock) { // 管程锁
Object[] es = this.getArray(); // 数组
int len = es.length;
if (index > len || index <0)
throw new IndexOutOfBoundsException(outOfBounds(index, len)); // 脚标检验
Object[] newElements; // 数组副本
int numMoved = len - index; // [index+1...]有多少个元素是要往后挪的
if (numMoved == 0)
newElements = Arrays.copyOf(es, len + 1); // 新增进来的元素就放在数组最后一个脚标处
else { // 新增进来的元素放在[0...len-1]数组中间的某个位置上 分两次拷贝idx之前和之后的元素
newElements = new Object[len + 1]; // 创建一个新的数组
System.arraycopy(es, 0, newElements, 0, index); // [0...idx-1]
System.arraycopy(es, index, newElements, index + 1,
numMoved); // [idx...len-1]
}
newElements[index] = element;
this.setArray(newElements); // 数组引用变更
}
}

CopyOnWriteArrayList#remove(int)

public E remove(int index) {
synchronized (lock) { // 管程锁
Object[] es = this.getArray(); // 数组
int len = es.length;
E oldValue = elementAt(es, index); // idx上的元素
int numMoved = len - index - 1;
Object[] newElements; // 数组副本
if (numMoved == 0)
newElements = Arrays.copyOf(es, len - 1); // 要移除的元素就是最后一个脚标len-1 拷贝[0...len-2]
else {
newElements = new Object[len - 1]; // 新数组的容量 要移除的元素在(0...len-1)中间
System.arraycopy(es, 0, newElements, 0, index); // 拷贝[0...idx-1]
System.arraycopy(es, index + 1, newElements, index,
numMoved); // 拷贝[idx+1...len-1]
}
this.setArray(newElements); // 数组引用
return oldValue;
}
}

 


总结



  • 迭代操作(读)不上锁 不影响性能

  • 涉及修改数据或者数据结构的操作(写)(add, set, remove...) => 空间换时间 synchronized{}代码块中的操作就是数据不一致的上限

    • 上锁(synchronized)保证线程安全

    • 不直接操作数据容器,拷贝一份副本进行操作,结束后改变指针引用



  • volatile关键字保证存储数据的数组线程可见性



推荐阅读
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • 分享一款基于Java开发的经典贪吃蛇游戏实现
    本文介绍了一款使用Java语言开发的经典贪吃蛇游戏的实现。游戏主要由两个核心类组成:`GameFrame` 和 `GamePanel`。`GameFrame` 类负责设置游戏窗口的标题、关闭按钮以及是否允许调整窗口大小,并初始化数据模型以支持绘制操作。`GamePanel` 类则负责管理游戏中的蛇和苹果的逻辑与渲染,确保游戏的流畅运行和良好的用户体验。 ... [详细]
  • 本文介绍了如何利用ObjectMapper实现JSON与JavaBean之间的高效转换。ObjectMapper是Jackson库的核心组件,能够便捷地将Java对象序列化为JSON格式,并支持从JSON、XML以及文件等多种数据源反序列化为Java对象。此外,还探讨了在实际应用中如何优化转换性能,以提升系统整体效率。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 深入解析Java虚拟机的内存分区与管理机制
    Java虚拟机的内存分区与管理机制复杂且精细。其中,某些内存区域在虚拟机启动时即创建并持续存在,而另一些则随用户线程的生命周期动态创建和销毁。例如,每个线程都拥有一个独立的程序计数器,确保线程切换后能够准确恢复到之前的执行位置。这种设计不仅提高了多线程环境下的执行效率,还增强了系统的稳定性和可靠性。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • 本文深入探讨了Java多线程环境下的同步机制及其应用,重点介绍了`synchronized`关键字的使用方法和原理。`synchronized`关键字主要用于确保多个线程在访问共享资源时的互斥性和原子性。通过具体示例,如在一个类中使用`synchronized`修饰方法,展示了如何实现线程安全的代码块。此外,文章还讨论了`ReentrantLock`等其他同步工具的优缺点,并提供了实际应用场景中的最佳实践。 ... [详细]
  • 链表作为一种与数组并列的基本数据结构,在Java中有着广泛的应用。例如,Java中的`ArrayList`基于数组实现,而`LinkedList`则是基于链表实现。链表在遍历操作时具有独特的性能特点,特别是在插入和删除节点时表现出色。本文将详细介绍单向链表的基本概念、操作方法以及在Java中的具体实现,帮助读者深入理解链表的特性和应用场景。 ... [详细]
  • 开发日志:201521044091 《Java编程基础》第11周学习心得与总结
    开发日志:201521044091 《Java编程基础》第11周学习心得与总结 ... [详细]
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
  • 在Android应用开发中,实现与MySQL数据库的连接是一项重要的技术任务。本文详细介绍了Android连接MySQL数据库的操作流程和技术要点。首先,Android平台提供了SQLiteOpenHelper类作为数据库辅助工具,用于创建或打开数据库。开发者可以通过继承并扩展该类,实现对数据库的初始化和版本管理。此外,文章还探讨了使用第三方库如Retrofit或Volley进行网络请求,以及如何通过JSON格式交换数据,确保与MySQL服务器的高效通信。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 在深入掌握Spring框架的事务管理之前,了解其背后的数据库事务基础至关重要。Spring的事务管理功能虽然强大且灵活,但其核心依赖于数据库自身的事务处理机制。因此,熟悉数据库事务的基本概念和特性是必不可少的。这包括事务的ACID属性、隔离级别以及常见的事务管理策略等。通过这些基础知识的学习,可以更好地理解和应用Spring中的事务管理配置。 ... [详细]
  • 深入浅析JVM垃圾回收机制与收集器概述
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》的阅读心得进行整理,详细探讨了JVM的垃圾回收机制及其各类收集器的特点与应用场景。通过分析不同垃圾收集器的工作原理和性能表现,帮助读者深入了解JVM内存管理的核心技术,为优化Java应用程序提供实用指导。 ... [详细]
author-avatar
手机用户2502938137
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有