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

news2024/11/16 19:24:07

异常简介

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.ConcurrentModificationException
 at 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

        @Synchronized
        fun 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.coloradmin.cn/o/1430299.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年度总结 | 关于意义,爱与回望——写给清醒又无知的20岁

Hi&#xff0c;大家好&#xff0c;我是半亩花海&#xff0c;一名再普通不过的大学生。2023年&#xff0c;20岁&#xff0c;充实而零乱的一年&#xff0c;清醒又无知的一年。年末&#xff0c;最近的一些事儿也让我逐渐地有感而发&#xff0c;心静&#xff0c;除杂&#xff0c;思…

2024年美国大学生数学建模比赛MCM问题B:搜索潜水器-思路解析与代码解答

2024 MCM Problem B Searching for Submersibles 一、题目翻译 背景&#xff1a; 总部位于希腊的小型海上巡航潜艇&#xff08;MCMS&#xff09;公司&#xff0c;制造能够将人类运送到海洋最深处的潜水器。潜水器被移动到该位置&#xff0c;并不受主船的束缚。MCMS现在希望用…

HTTP和HTTPS区别!

http 是我们几乎天天都要打交道的东西&#xff0c;相关知识点有点多&#xff0c;所以也有不少面试必问的点&#xff0c;这里做了一些整理&#xff0c;帮且大家树立完整的 http 知识体系&#xff0c;对面试官说 so easy HTTP 的特点和缺点 特点&#xff1a;无连接、无状态、灵…

【HarmonyOS应用开发】Web组件的使用(十三)

文章末尾含&#xff1a;Web组件抽奖案例&#xff08;ArkTS&#xff09;-示例源码下载 Web组件的使用 一、概述 相信大家都遇到过这样的场景&#xff0c;有时候我们点击应用的页面&#xff0c;会跳转到一个类似浏览器加载的页面&#xff0c;加载完成后&#xff0c;才显示这个页…

vivado 运行编译

运行合成 运行定义并配置在合成过程中使用的设计方面。一个合成run定义了以下内容&#xff1a; •AMD设备在合成过程中成为目标 •要应用的约束集 •启动单个或多个合成运行的选项 •控制合成引擎结果的选项 要定义RTL源文件和约束的运行&#xff0c;请执行以下操作&…

Java 数据结构 二叉树(二)红黑树

目录 数据结构图-树 简介 规则 旋转 重新着色 红黑树构建过程 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入工作的漩涡&#xff0c;忘记了停下脚步&#xf…

关于华为应用市场上架,申请权限未告知目的被驳回问题的简单处理方式

关于华为应用市场上架过程中出现的【您的应用在运行时&#xff0c;未同步告知权限申请的使用目的&#xff0c;向用户索取&#xff08;存储、拍照&#xff09;等权限&#xff0c;不符合华为应用市场审核标准。】 使用方式&#xff1a; 1、引入 import permision from "/m…

配置实例—交换机VLAN聚合配置实例

一、组网需求 某公司拥有多个部门且位于同一网段&#xff0c;为了提升业务安全性&#xff0c;将不同部门的用户划分到不同VLAN中。现由于业务需要&#xff0c;不同部门间的用户需要互通。如图1所示&#xff0c;VLAN2和VLAN3为不同部门&#xff0c;现需要实现不同VLAN间的用户可…

【MySQL】学习如何通过DQL进行数据库数据的基本查询

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-KvH5jXnPNsRtMkOC {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

Java学习day28:线程池Pool(知识点非常非常的详解)

声明&#xff1a;该专栏本人重新过一遍java知识点时候的笔记汇总&#xff0c;主要是每天的知识点题解&#xff0c;算是让自己巩固复习&#xff0c;也希望能给初学的朋友们一点帮助&#xff0c;大佬们不喜勿喷(抱拳了老铁&#xff01;) 往期回顾 Java学习day27&#xff1a;join方…

YOLOv5算法进阶改进(15)— 引入密集连接卷积网络DenseNet

前言:Hello大家好,我是小哥谈。DenseNet(密集连接卷积网络)是一种深度学习神经网络架构,它在2017年由Gao Huang等人提出。DenseNet的核心思想是通过密集连接(dense connection)来促进信息的流动和共享。在传统的卷积神经网络中,每个层的输入只来自于前一层的输出。而在…

c语言---操作符(详解)

目录 一、操作符的分类二、算术操作符三、 移位操作符3.1<<左移操作符3.1.1移位规则3.1.2直接上代码以及解释 3.2>> 右移操作符3.2.1移位规则3.2.2画图解释 3.3注意 四、位操作符&#xff1a;&、|、^、~4.1&按位与4.1.1按位与的计算逻辑4.1.2代码4.1.3运行…

Modbus协议学习第七篇之libmodbus库API介绍(modbus_write_bits等)

写在前面 在第六篇中我们介绍了基于libmodbus库的演示代码&#xff0c;那本篇博客就详细介绍一下第六篇的代码中使用的基于该库的API函数。另各位读者&#xff0c;Modbus相关知识受众较少&#xff0c;如果觉得我的专栏文章有帮助&#xff0c;请一定点个赞&#xff0c;在此跪谢&…

redis布隆过滤器(Bloom)详细使用教程

文章目录 布隆过滤器1. 原理2. 结构和操作3. 特点和应用场景4. 缺点和注意事项 应用-redis插件布隆过滤器使用详细过程安装以及配置springboot项目使用redis布隆过滤器下面是布隆过滤器的一些基础命令 扩展 布隆过滤器 Bloom 过滤器是一种概率型数据结构&#xff0c;用于快速判…

算法学习——华为机考题库5(HJ31 - HJ35)

算法学习——华为机考题库5&#xff08;HJ31 - HJ35&#xff09; HJ31 单词倒排 描述 对字符串中的所有单词进行倒排。 说明&#xff1a; 1、构成单词的字符只有26个大写或小写英文字母&#xff1b; 2、非构成单词的字符均视为单词间隔符&#xff1b; 3、要求倒排后的单…

Jmeter,如何从数组参数中取值

有个post请求&#xff0c;参数“equipment_ids”&#xff0c;是个数组&#xff0c;需求每次执行的时候&#xff0c;按顺序取equipment_ids中不同的值 要实现在 JMeter 中每次执行请求时按顺序取不同的 equipment_ids 中的值&#xff0c;你可以使用 Counter 元件来生成索引&…

Linux 磁盘空间占用率100%的排查

&#x1f4d1;前言 使用 Linux 操作系统时&#xff0c;可能会遇到磁盘空间不足的错误&#xff0c;这种错误通常会导致系统运行缓慢或崩溃。本文将介绍磁盘排查的方法。⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1…

【深入浅出Java性能调优】「底层技术原理体系」详细分析探索Java服务器性能监控Metrics框架的实现原理分析(Dropwizard度量基础案例指南)

深入探索Java服务器性能监控Metrics框架的实现原理分析 前提介绍Dropwizard MetricsDropwizard的特点Dropwizard的开发案例需要引入Maven依赖常用度量类型Meter(每秒请求数为单位测量请求率)定义度量核心MetricRegistry构建对应的Meter指标对象请求标记采样业务方法控制报告器…

【C++入门到精通】C++的IO流(输入输出流) [ C++入门 ]

阅读导航 引言一、C语言的输入与输出二、流是什么三、CIO流1. C标准IO流&#xff08;1&#xff09;istream&#xff08;2&#xff09;ostream&#xff08;3&#xff09;iostream&#xff08;4&#xff09;cin 和 cout 2. C文件IO流&#xff08;1&#xff09;ifstream&#xff0…

WPF简介

WPF早期名称为Avalon&#xff0c;是微软的一个项目&#xff0c;目的是构建统一的平面展示平台 WPF是一个与分辨率无关的UI框架&#xff0c;是一种基于矢量的呈现引擎技术&#xff1b;所用的语言为XAML(全称Extensible Application MarkupLanguage)&#xff0c;它的基本度量单位…