ConcurrentModificationException异常原因,解决方法,线程安全的单例模式

异常简介

ConcurrentModificationException(并发修改异常)是基于java集合中的 快速失败(fail-fast) 机制产生的,在使用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了增删改,就会抛出该异常。
快速失败机制使得java的集合类不能在多线程下并发修改,也不能在迭代过程中被修改。

异常原因

示例代码

val elements : MutableList<Int> = mutableListOf()
for ( i in 0..100) {//添加元素elements.add(i)
}val thread = Thread {//线程一读数据elements.forEach {Log.i("testTag", it.toString())}
}val thread2 = Thread {//线程二写入数据for (i in 1..100) {elements.add(i)}
}thread.start()
thread2.start()抛出异常:java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.next(ArrayList.java:860)

异常原因是什么呢?
modCount:表示list集合结构上被修改的次数
expectedModCount:表示对ArrayList修改次数的期望值(在开始遍历元素之前记录的)
list的for循环中是通过Iterator迭代器遍历访问集合内容,在遍历过程中会使用到modCount变量,如果在遍历过程期间集合内容发生变化,则会改变modCount的数值,每当迭代器使用next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedModCount 值,相等的话就返回遍历;否则抛出异常(ConcurrentModificationException),终止遍历。
在这里插入图片描述

而在我们的示例代码中,线程二在调用add方法的时候modCount+1,导致线程一在遍历的时候modCount!=expectedModCount,所以抛出了ConcurrentModificationException

解决方法

那在多线程下,我们需要集合支持并发读写怎么实现呢?

  1. 使用Collections.synchronizedList给集合加锁
val elements : MutableList<Int> = Collections.synchronizedList(mutableListOf())
...
val thread = Thread {//线程一读数据synchronized(elements) {//使用Iterator遍历时需要手动加锁elements.forEach {Log.i("testTag", it.toString())}}
}
...

原理:
以组合的方式将对 List 的接口方法操作,委托给传入的 list 对象,并且对所有的接口方法对象加锁,得到并发安全性。通过组合的方式对传入的list对象的get,set,add等方法加synchronized同步锁,但是对于需要用到iterator迭代器的时候需要手动加锁

public static <T> List<T> synchronizedList(List<T> list) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList<>(list) :new SynchronizedList<>(list));
}static <T> List<T> synchronizedList(List<T> list, Object mutex) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList<>(list, mutex) :new SynchronizedList<>(list, mutex));
}SynchronizedCollection(Collection<E> c) {this.c = Objects.requireNonNull(c);//需要加锁的对象,这里指自己mutex = this;
}static class SynchronizedList<E>extends SynchronizedCollection<E>implements List<E> {private static final long serialVersionUID = -7754090372962971524L;final List<E> list;SynchronizedList(List<E> list) {super(list);this.list = list;}SynchronizedList(List<E> list, Object mutex) {super(list, mutex);this.list = list;}//在list提供的方法外加了synchronized同步锁public boolean equals(Object o) {if (this == o)return true;synchronized (mutex) {return list.equals(o);}}public int hashCode() {synchronized (mutex) {return list.hashCode();}}public E get(int index) {synchronized (mutex) {return list.get(index);}}public E set(int index, E element) {synchronized (mutex) {return list.set(index, element);}}public void add(int index, E element) {synchronized (mutex) {list.add(index, element);}}public E remove(int index) {synchronized (mutex) {return list.remove(index);}}public int indexOf(Object o) {synchronized (mutex) {return list.indexOf(o);}}public int lastIndexOf(Object o) {synchronized (mutex) {return list.lastIndexOf(o);}}public boolean addAll(int index, Collection<? extends E> c) {synchronized (mutex) {return list.addAll(index, c);}}//使用iterator迭代器的时候需要手动加锁public ListIterator<E> listIterator() {return list.listIterator(); // Must be manually synched by user}public ListIterator<E> listIterator(int index) {return list.listIterator(index); // Must be manually synched by user}

优点:可以使非线程安全的集合如Arraylist封装成线程安全的集合,并且相对CopyOnWriteArrayList写操作性能较好
缺点:在任何操作之前都需要加同步锁,使用iterator还需要手动加锁才能保证并发读写安全
2. 使用支持并发读写的CopyOnWriteArrayList

val elements : CopyOnWriteArrayList<Int> = CopyOnWriteArrayList()

原理:

public E get(int index) {return get(getArray(), index);
}public boolean add(E e) {synchronized (lock) {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;setArray(newElements);return true;}
}
...

读操作:直接读数组对应位置的数据
写操作:以add方法为例,在执行add方法时,会先对集合对象添加同步锁,然后创建一个len+1的数组,再把旧数组中数据复制添加到新数组中,最后把新数组替换掉老数组
优点:读操作效率高,无加锁操作
缺点:写操作每次都需要复制一份新数组,性能较差

拓展:多线程下怎么做好单例的设计

懒汉式单例

在需要的时候再去创建实例。
锁它!锁它!锁它!

同步锁

Java

public class SingleTon {private static volatile SingleTon instance;private SingleTon() {}public static SingleTon getInstance() {synchronized (SingleTon.class) {if (instance == null) {instance = new SingleTon();}}   return instance;}
}

Kotlin

class SingleTon {companion object {private var instance: SingleTon? = null@Synchronizedfun getInstance(): SingleTon {if (instance == null) {instance = SingleTon()}return instance!!}}
}

优点:线程安全,可以延时加载。
缺点:调用效率不高(有锁,且需要先创建对象)。

DCL

为提升性能,减小同步锁的开销,避免每次获取实例都需要经过同步锁,可以使用双重检测判断实例是否已经创建。
Java

public class SingleTon {private static volatile SingleTon4 instance;private SingleTon() {}public static SingleTon getInstance() {if (instance == null) {synchronized (SingleTon.class) {if (instance == null) {instance = new SingleTon4、();}}}return instance;}
}

Kotlin

class SingleTon4 {companion object {val instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {SingleTon4()}}
}

饿汉式单例

在类被加载的时候就把Singleton实例给创建出来供使用,以后不再改变。
Java

public class SingleTon {private static SingleTon singleTon = new SingleTon();private SingleTon() {}public static SingleTon getInstance() {return singleTon;}
}

Kotlin

object SingleTon1 {}

优点:实现简单, 线程安全,调用效率高(无锁,且对象在类加载时就已创建,可直接使用)。
缺点:可能在还不需要此实例的时候就已经把实例创建出来了,不能延时加载(在需要的时候才创建对象)。

静态内部类

静态内部类只有被主动调用的时候,JVM才会去加载这个静态内部类。外部类初次加载,会初始化静态变量、静态代码块、静态方法,但不会加载内部类和静态内部类。
Java

public class Singleton {private Singleton() {}public static Singleton getInstance() {return SingletonFactory.instance;}private static class SingletonFactory {private static Singleton instance = new Singleton();}}

Kotlin

class SingleTon5 {companion object {fun getInstance() = Holder.instance}private object Holder {val instance = SingleTon5()}
}

优点:线程安全,调用效率高,可以延时加载。

枚举类

最佳的单例实现模式就是枚举模式。写法简单,线程安全,调用效率高,可以天然的防止反射和反序列化调用,不能延时加载。
Java

public enum Singleton {INSTANCE;public void show() {System.out.println("show");}}调用Singleton.INSTANCE.show();

Kotlin

enum class Singleton {INSTANCE;fun show() {println("show")}
}
写在最后:

在线程安全的几种单例中
枚举(无锁,调用效率高,可以防止反射和反序列化调用,不能延时加载)> 静态内部类(无锁,调用效率高,可以延时加载) > 双重同步锁(有锁,调用效率高于懒汉式,可以延时加载) > 懒汉式(有锁,调用效率不高,可以延时加载) ≈ 饿汉式(无锁,调用效率高,不能延时加载)

ps:只有枚举能防止反射和反序列化调用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/450138.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

配置nginx作为静态文件托管服务器

下载nginx windows上是个压缩包 解压后, 使用命令行输入 nginx 进行启动 nginx -s stop 进行停止 nginx -s status 查看状态 可以配置一下环境变量 主要是配置文件, windows的nginx配置文件在 conf文件夹下 在http标签下 添加如下配置 其他地方不用更改,保持原样即可, 以…

Cmake语法学习2:常用变量

目录 1.常用变量简介 1.1提供信息的变量 1.2改变行为的变量 1.3描述系统的变量 ​编辑1.4控制编译的变量 2.提供信息的变量 2.1PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR 2.2 CMAKE_SOURCE_DIR 和 CMAKE_BINARY_DIR 2.3CMAKE_CURRENT_SOURCE_DIR 和CMAKE_CURRENT_BIN…

计算机网络自顶向下Wireshark labs-HTTP

我直接翻译并在题目下面直接下我的答案了。 1.基本HTTP GET/response交互 我们开始探索HTTP&#xff0c;方法是下载一个非常简单的HTML文件 非常短&#xff0c;并且不包含嵌入的对象。执行以下操作&#xff1a; 启动您的浏览器。启动Wireshark数据包嗅探器&#xff0c;如Wir…

【lesson8】高并发内存池Central Cache层释放内存的实现

文章目录 Central Cache层释放内存的流程Central Cache层释放内存的实现 Central Cache层释放内存的流程 当thread_cache过长或者线程销毁&#xff0c;则会将内存释放回central cache中的&#xff0c;释放回来时–use_count。当use_count减到0时则表示所有对象都回到了span&am…

[MFC] MFC消息机制的补充

之前写了[MFC] 消息映射机制的使用和原理浅析&#xff0c;还有些需要补充的&#xff0c;都记在这里。 MFC 消息的分类 MFC消息分为系统消息和自定义消息。 图片来源&#xff1a;C语言/C教程 大型源码案例分析&#xff1a;MFC消息系统的代码解析 易道云编程 系统消息分为窗口…

YOLOv5改进:下采样系列 |一种新颖的基于 Haar 小波的下采样HWD,有效涨点系列

💡💡💡本文独家改进:HWD的核心思想是应用Haar小波变换来降低特征图的空间分辨率,同时保留尽可能多的信息,与传统的下采样方法相比,有效降低信息不确定性。 💡💡💡使用方法:代替原始网络的conv,下采样过程中尽可能包括更多信息,从而提升检测精度。 收录 YO…

Python中的继承和super()

父类方法的调用 可以使用父类名.方法名来调用也可以使用super().方法名来调用 MRO顺序 Python官方采用了一个算法将复杂结构上所有的类全部都映射到一个线性顺序上&#xff0c;而根据这个顺序就能 够保证所有的类都会被构造一次。这个顺序就是MRO顺序。 查看MRO顺序 类名.…

【JavaWeb】头条新闻纯JavaWeb项目实现 项目搭建 数据库工具类导入 跨域问题 Postman 第一期 (前端Vue3+Vite)

文章目录 一、项目简介1.1 微头条业务简介1.2 技术栈介绍 二、项目部署三、准备工具类3.1 异步响应规范格式类3.2 MD5加密工具类3.3 JDBCUtil连接池工具类3.4 JwtHelper工具类3.4 JSON转换的WEBUtil工具类 四、准备各层的接口和实现类4.1 准备实体类和VO对象4.2 DAO层接口和实现…

Day17、18、19学习记录

#c语言知识 内存管理 1.作用域 &#xff08;1&#xff09;代码块作用域&#xff08;代码块是{}之间的一段代码&#xff09; &#xff08;2&#xff09;函数作用域 &#xff08;3&#xff09;文件作用域 2.局部变量&#xff08;自动变量auto&#xff09;&#xff1a; 在函…

快速理解复杂系统组成学习内容整合

目录 一、复杂系统组成 二、接入系统 (Access System) 三、应用系统 (Application System) 四、基础平台 (Foundation Platform) 五、中间件 (Abundant External Middleware) 六、支撑系统 (Supporting System) 参考文章 一、复杂系统组成 复杂系统是由多个相互关联、相…

2024程序员就业咋办?

国际研究机构Gartner会在每年10月份左右发布下一年度的战略发展趋势预测&#xff0c;并在次年3月左右发布和网络安全相关的趋势预测。绿盟科技通过将近3年的趋势预测进行分组对比分析后发现&#xff0c;除了众人皆知的AI技术应用外&#xff0c;数据模块化、身份优先安全、行业云…

【Java程序设计】【C00207】基于(JavaWeb+SSM)的宠物领养管理系统(论文+PPT)

基于&#xff08;JavaWebSSM&#xff09;的宠物领养管理系统&#xff08;论文PPT&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的宠物领养系统 本系统分为前台系统、管理员、收养者和寄养者4个功能模块。 前台系统&#xff1a;游客打开系统…