报错:java.util.ConcurrentModificationException 线程不安全
创始人
2024-05-28 19:43:55
0

        今天在做项目时,遇到个bug,需求是app在特定的时候把广告缓存下来,合适的位置在展示,于是想的用hashmap列表存起来,key为位置的广告类型,value为广告对象。为了保证每个位置同时缓存的对象只有一个,于是在存储(add方法)前会将列表遍历一遍,如果有就remove掉。但是请求广告有时候并不在一个线程,于是今天在运行时,App闪退了。

报错:java.util.ConcurrentModificationException

可翻译成“并发修改异常”。

原因:触发fast-fail机制

fail-fast机制就是为了防止多线程修改集合造成并发问题的机制。

“快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。
 

报错代码:

list?.forEach {if (it["type"].toString() ==type)  list?.remove(it)}

koltin的foreach其实就是java的增强for循环(类似于for(Object a:list){})。这行代码实现的目标是想要在循环遍历的过程中找到指定数据并删除集合中的元素。

去官方文档查询,我这个应该是线程不安全导致的,想来也是,静态变量全局调用,本就不是线程安全的。所以当我们迭代一个ArrayList或者HashMap时,如果尝试对集合做一些修改操作(例如删除元素),可能会抛出java.util.ConcurrentModificationException的异常。

我原来用的数据结构是MutableList,继承于Collection,实现了Iterable接口。

foreach(java的for循环)的背后实现原理其实就是Iterator,迭代列表的Iterator中有一个变量expectedModCount,该变量会初始化和modCount相等,但如果接下来如果集合进行修改modCount改变,就会造成expectedModCount!=modCount,此时就会抛出java.util.ConcurrentModificationException异常。就是你的modCount改变了,但是还在迭代,导致expectedModCount和modCount不一样,导致报错。

之所以有modCount(修改次数)和expectedModCount(预期修改次数)这两个变量,就是为了辨别多线程修改集合时出现的错误。(细节可去查更多看源码)

源码

public class ArrayList extends AbstractListimplements List, RandomAccess, Cloneable, java.io.Serializable
{... .../*** An optimized version of AbstractList.Itr*/private class Itr implements Iterator {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;// prevent creating a synthetic constructorItr() {}public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {checkForComodification();//checkForComodification方法检测modcount和expectedModCount是否相同int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}@Overridepublic void forEachRemaining(Consumer action) {Objects.requireNonNull(action);final int size = ArrayList.this.size;int i = cursor;if (i < size) {final Object[] es = elementData;if (i >= es.length)throw new ConcurrentModificationException();for (; i < size && modCount == expectedModCount; i++)action.accept(elementAt(es, i));// update once at end to reduce heap write trafficcursor = i;lastRet = i - 1;checkForComodification();}}//检查计数是否相同final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}}... ...

由于我的广告很多时候都在加载(add操作),展示(remove操作),所以容易导致modCount和expectedModCount不相同,于是,便可以从线程安全下手。

方法一:加锁,Android里常会用到synchronized,在迭代前先枷锁,解决了多线程问题,但还是不能进行迭代add、clear等操作。不过我不迭代操作,也可以用。但是操作的地方太多了,不方便。

list?.let {synchronized(it) {list?.forEach {it1->if (it1["type"].toString() == type) list?.remove(it1)}}}                               

方法二:CopyOnWriteArrayList,解决了多线程问题,可以执行各种等操作。

直接在定义的时候定义成CopyOnWriteArrayList就行了。

    var cacheDataList: CopyOnWriteArrayList>? = CopyOnWriteArrayList()

原理:CopyOnWriteArrayList是一个线程安全的ArrayList,CopyOnWrite容器即写时复制的容器,这是一种延时懒惰策略。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。CopyOnWrite并发容器用于读多写少的并发场景(可能以后还得优化一下,因为广告读写都挺多的,内存不大友好,或者可以试试ConcurrentHashMap)。

缺点

1.内存问题.因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存

2.数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

方法三:不用默认的迭代器方法,而是获取到迭代器再去操作列表,这个是看的另一个方法,我没用。(此方法在单线程估计没问题,多线程不确定)

 System.out.println("====迭代器遍历");Iterator iterator = list.iterator();while (iterator.hasNext()) {Object next = iterator.next();//进行删除等操作}

相关内容

热门资讯

谷歌框架和安卓系统,构建智能移... 你有没有想过,为什么你的手机那么聪明,能帮你找到路线,还能帮你拍出美美的照片呢?这都要归功于一个超级...
安卓系统和oppo系统哪个流畅... 你有没有想过,手机系统哪个更流畅呢?安卓系统和OPPO系统,这两个名字听起来就让人心动。今天,咱们就...
安卓怎么用微软系统,利用微软系... 你是不是也和我一样,对安卓手机上的微软系统充满了好奇?想象那熟悉的Windows界面在你的安卓手机上...
安卓系统如何安装nfc,安卓系... 你有没有想过,用手机刷公交卡、支付账单,是不是比掏出钱包来得酷炫多了?这就得归功于NFC技术啦!今天...
ios系统可以转安卓,跨平台应... 你有没有想过,你的iPhone手机里的那些宝贝应用,能不能搬到安卓手机上继续使用呢?没错,今天就要来...
iOSapp移植到安卓系统,i... 你有没有想过,那些在iOS上让你爱不释手的app,是不是也能在安卓系统上大放异彩呢?今天,就让我带你...
现在安卓随便换系统,探索个性化... 你知道吗?现在安卓手机换系统简直就像换衣服一样简单!没错,就是那种随时随地、随心所欲的感觉。今天,就...
安卓系统安装按钮灰色,探究原因... 最近发现了一个让人头疼的小问题,那就是安卓手机的安装按钮突然变成了灰色,这可真是让人摸不着头脑。你知...
安卓7.1.1操作系统,系统特... 你知道吗?最近我在手机上发现了一个超级酷的新玩意儿——安卓7.1.1操作系统!这可不是什么小打小闹的...
安卓os系统怎么设置,并使用`... 你有没有发现,你的安卓手机有时候就像一个不听话的小孩子,有时候设置起来真是让人头疼呢?别急,今天就来...
安卓降低系统版本5.1,探索安... 你知道吗?最近安卓系统又来了一次大动作,竟然把系统版本给降到了5.1!这可真是让人有点摸不着头脑,不...
解放安卓系统被保护,解放安卓系... 你有没有想过,你的安卓手机其实可以更加自由地呼吸呢?是的,你没听错,我说的就是解放安卓系统被保护的束...
校务帮安卓系统下载,便捷校园生... 你有没有想过,你的手机里装了一个神奇的助手——校务帮安卓系统下载?没错,就是那个能让你轻松管理学校事...
安卓系统没有拼多多,拼多多崛起... 你知道吗?最近我在手机上发现了一个小小的秘密,那就是安卓系统里竟然没有拼多多这个应用!这可真是让我大...
甜城麻将安卓系统,解锁全新麻将... 你有没有听说过那个超级火的甜城麻将安卓系统?没错,就是那个让无数麻将爱好者为之疯狂的软件!今天,就让...
安卓系统卸载的软件,深度揭秘卸... 手机里的软件越来越多,是不是感觉内存不够用了?别急,今天就来教你怎么在安卓系统里卸载那些不再需要的软...
安卓系统推荐好游戏,畅享指尖乐... 手机里的游戏可是咱们休闲娱乐的好伙伴,尤其是安卓系统的用户,选择面那可是相当广呢!今天,就让我来给你...
王者安卓系统怎么卖,揭秘如何轻... 你有没有听说最近王者安卓系统的火爆程度?没错,就是那个让无数玩家沉迷其中的王者荣耀!今天,我就来给你...
安卓开发系统内置证书,基于安卓... 你有没有想过,你的安卓手机里那些神秘的内置证书,它们到底是个啥玩意儿?别急,今天就来给你揭秘这些隐藏...
荣耀安装安卓原生系统,深度体验... 你知道吗?最近荣耀手机界可是掀起了一股热潮,那就是——荣耀安装安卓原生系统!这可不是什么小打小闹,而...