呔!被ConcurrentModificationException追了尾!!!

1,199 阅读3分钟

起因:

image.png

**图:1**

疑问:为什么简单的一个foreach循环时不时会报ConcurrentModificationException

(补充:该异常的原因是jdk1.8之后专家们对集合增加的一种快速失败机制,在遍历过程中会先效验两个变量 expectedModCount和modCount)

思考:

1、是因为在循环的过程中改变了集合的数量???

2、是因为线程之间享用了共享资源???

小节:由图(1)可知,此处的foreach里未对集合数量做出增减动作,此处排除猜想一

上车:

准备:我们知道Java的foreach实际上是个语法糖,编译后实际上是用的Iterator这个内部类进行集合的遍历;

条件:已知异常是ConcurrentModificationException 遍历方式是Iterator

发车:

咱们走进巨人的世界:

1、首先定位list集合里的私有内部类Iterator

图2

跟着代码执行流我们看到上图next()函数里的第一行这个函数checkForComodification()

图三

此时这里为什么要判断 modCount != expectedModCount 先不关心,但是这个异常不就是我们想要找的那个嘛;

异常位置定位到了,那我们就跟踪下modCount != expectedModCount 是什么含义吧!!!

由图二可知

expectedModCount是一个私有内部类变量

那么modCount 它是谁???

图四

原来是一个抽象类的类变量

此时我们结合类加载机制回想下 expectedModCount 和 modCount会分别放在jvm内存的哪个区域

expectedModCount所属private class Itr implements Iterator<E> {} 私有内部类

modCount 所属 public abstract class AbstractList < E > extends AbstractCollection < E > implements List < E > {} 抽象类

  • 知识点扩充:多线程情况下jvm怎么确保对象内存分配的安全性?
    •    采用的是:线程本地缓存法(TLAB)
    •   多线程情况下jvm怎么确保只创建一个对象?
    •    采用的是: 类加载器之双亲委派机制来避免类的重复加载
    •   由上面两个知识点我们得知:
    •   AbstractList是外部类 ,我们知道外部类的成员变量会放到堆中,
    •   堆的特点又是线程共享,那modCount 线程共享;
    •    私有内部类的成员变量虽然在堆中但是是线程之间隔离的; expectedModCount 线程私有;
    •   
    •   图五
    •   图六
    •   图七
    •   由图五-图七可知:
    •   arraylist.remove()函数 修改了成员变量modCount的值;
    •   此时多线程场景下:
    •   一号线程获取到list结果集为modCount赋值 开始foreach的过程中由于一开始cpu分配到一号线程的时间片较短,本次只执行了一半,就被缓存起来了;
    •   二号线程获取到list结果集为modCount赋值开始执行foreach ,由于两个线程查到的集合数量不一致,modCount值会不同;(我们知道modCount是线程共享的)
    •   此时二号线程本次的时间片到期,cpu唤起一号线程开始执行,当一号线程执行到next()函数的时候,我们看图二的997行,发现有个效验
    •   if(modCount != expectedModCount){
    •   throw new ConcurrentModificationException() ;
    •   }
    •   此时expectedModCount是一号线程list的数量 modCount是二号线程list的数量
    •   结果: emm 抛出了ConcurrentModificationException;