网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
通过反编译可以发现foreach编译后内部是使用迭代器实现的。
迭代器是通过list.iterator()实例化的,list.iterator()就返回了一个内部类Itr的对象,从源码中可以看到Itr实现了Iterator接口,同时声明了expectedModCount这个成员变量, expectedModCount 表示对ArrayList修改次数的期望值,它的初始值为 modCount。
2.3、熟悉的checkForComodification方法
从源码可以看到这个类的next和remove方法里面都调用了一个checkForComodification方法,看到checkForComodification是不是很熟悉,这不就是异常的抛出位置吗。
checkForComodification方法是通过判断modCount和expectedModCount是否相等来决定是否抛出并发修改异常。
2.4、流程回顾
通过查看编译后的class文件,可以看出大致流程如下:当j为3时,调用了remove方法,remove方法中修改了modCount值,然后再输出j值,再进入下一次循环,此时hasNext为true,进入循环体第一行代码,调用next方法,next方法再调用checkForComodification方法,然后发现expectedModCount和modCount不一致,最终抛出ConcurrentModificationException 异常。
也就是说,expectedModCount 初始化为 modCount 了,但是后面 expectedModCount 没有修改,而在 remove 和 add 的过程中修改了modCount ,这就导致了执行的时候,通过 checkForComodification 方法来判断两个值是否相等,如果相等了,那么没问题,如果不相等,那就给你抛出一个异常来。
而这也就是我们通俗说起来的 fail-fast 机制,也就是快速检测失败机制。
3、避免fail-fast 机制
3.1、使用listIterator或iterator
fail-fast 机制也是可以避免的,比如再拿出来我们上面的代码
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
System.out.println("没有删除元素前"+list.toString());
// 迭代器使用listIterator和iterator均可
ListIterator<Integer> listIterator = list.listIterator();
while(listIterator.hasNext()){
Integer integer = listIterator.next();
if(integer==3){
listIterator.remove();
listIterator.add(9);
}
}
System.out.println("删除元素后"+list.toString());
}
这样的话,你就发现是可以运行的,也是没有问题的,我们看运行结果:
没有删除元素前[0, 1, 2, 3, 4]
删除元素后[0, 1, 2, 9, 4]
结果也是显而易见的,我们实现了在 foreach 中进行 add 和 remove 的操作.
这里有个注意点,迭代器使用listIterator和iterator均可,看源码可以知道 listIterator其实使用的ListItr内部类,ListItr是继承了Itr类的,同时自己封了一些方法,例如add,hasPrevious,previous等等。所以代码中的remove方法是Itr类的,add方法是ListItr类的
listIterator和iterator区别:
- 使用范围不同,Iterator可以应用于所有的集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型。
- ListIterator有add方法,可以向List中添加对象,而Iterator不能。
- ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以。
- ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
- 都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。
3.2、使用CopyOnWriteArrayList
CopyOnWriteArrayList 这个类也是能解决 fail-fast 的问题的,我们来试一下:
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
System.out.println("没有删除元素前"+list.toString());
for (Integer integer : list) {
if(integer.equals(3)){
list.remove(3);
list.add(9);
}
}
System.out.println("删除元素后"+list.toString());
}
运行结果:
没有删除元素前[0, 1, 2, 3, 4]
删除元素后[0, 1, 2, 4, 9]
CopyOnWriteArrayList实现了对这个元素中间进行移除添加的操作,那么他的内部源码是怎么实现的,实际上很简单,复制
也就是他创建一个新的数组,再将旧的数组复制到新的数组上,但是为什么很少有人推荐这种做法,根本原因还是 复制
因为你使用了复制,那么就一定会出现有两个存储相同内容的空间,这样消耗了空间,最后进行 GC 的时候,那是不是也需要一些时间去清理他,所以个人不是很推荐,但是写出来的必要还是有的。
3.2.1、CopyOnWriteArrayList的add方法
public boolean add(E e) {
// 可重入锁
final ReentrantLock lock = this.lock;
// 获取锁
lock.lock();
try {
// 元素数组
Object[] elements = getArray();
// 数组长度
int len = elements.length;
// 复制数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 存放元素e
newElements[len] = e;
// 设置数组
setArray(newElements);
return true;
} finally {
// 释放锁
lock.unlock();
}
}
处理流程如下:
- 获取锁(保证多线程的安全访问),获取当前的Object数组,获取Object数组的长度为length,进入步骤②。
- 根据Object数组复制一个长度为length+1的Object数组为newElements(此时,newElements[length]为null),进入下一步骤。
- 将下标为length的数组元素newElements[length]设置为元素e,再设置当前Object[]为newElements,释放锁,返回。这样就完成了元素的添加。
3.2.2、CopyOnWriteArrayList的remove方法
public E remove(int index) {
// 可重入锁
final ReentrantLock lock = this.lock;
// 获取锁
lock.lock();
try {
// 获取数组
Object[] elements = getArray();
// 数组长度
int len = elements.length;
// 获取旧值
E oldValue = get(elements, index);
![img](https://img-blog.csdnimg.cn/img_convert/6ed5dcae37c1e628d74563931dad1bae.png)
![img](https://img-blog.csdnimg.cn/img_convert/78ea0392f4f96a38ce01f75ed782ac22.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
80946)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**