前要:在看一道题,看到一题ArrayList的remove,问如下是否会报错
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
String reString = "张三";
list.add("张三");
list.add("李四");
list.add("王五");
list.add("小明");
for (String n : list) {
if (reString.equals(n)) {
list.remove(n);
}
}
}
运行代码,报错如下:
我看了别人的文章,发现别人只是说modCount发生改变,但是具体是如何改变的就没有细说,感觉不够细致。
思考过程:
之前我看过解决的方案使用Iterator迭代器进行遍历。
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
String reString = "张三";
list.add("张三");
list.add("李四");
list.add("王五");
list.add("小明");
// 报错:ConcurrentModificationException
// for (String n : list) {
// if (reString.equals(n)) {
// list.remove(n);
// }
// }
//iterator 正确写法
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
if (iterator.next().equals(reString)) {
iterator.remove();
}
}
for (String n:list) {
System.out.println(n);
}
}
执行结果:执行结果和预期结果一致,正确
思考:想着都是使用了remove,有什么不同么?
抱着这样的思考打断点,查看ArrayList源码
首先看ForEach增强循环中的remove源码,如下:
ArrayList源码
此时modCount因为增加了remove *** 作,所以加一,变成了5
接下来,就是报错的关键了,因为ForEach语法糖中的next了,如下
因为modCount(修改次数:5次)和expectedModCount(预计修改的次数:4次)不相等,因为多了一次remove *** 作,所以直接报错。
但我看到这里,我在想,难道使用iterator迭代器遍历remove难道有什么不同么?
果然,在我一次次打断点查看后,发现关键因素是Iterator迭代器本身就是一个内部类,这里的remove是使用的内部类中到方法
Iterator源码:
Fast-Fail机制
通过这个问题,我就衍生出了Fast-Fail机制,其实该机制就是但线程a为了对非线程安全的数据进行遍历时,线程b对数据进行修改,则modCount发生改变,线程a再下一次遍历时会通过checkForComodification()方法检测,判断modCount更改,从而直接报错。
解决方案:
面对并发问题,并发Java中的Copy-On-Write容器可以很好的解决该问题
Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。
大佬文章:
Fast-Fail机制
收获与总结:
- forEach语法糖是使用了hasNext()、next()方法
- 对于ArrayList进行数据的修改,需要直接使用Iterator迭代器进行修改
- 面对Fast-Fail机制的并发问题,可以使用CoryOnWrite容器,扩展CopyOnWrite特性
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)