A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 龚冼敏老师 初级黑马   /  2016-6-20 17:40  /  8854 人查看  /  101 人回复  /   4 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 龚冼敏老师 于 2016-6-20 17:53 编辑

在学习集合这一块的时候, 如果我们在使用迭代器对集合进行遍历, 然后在遍历的过程中, 调用了集合中的remove方法对集合进行删除元素, 那么这个时候就会发生并发修改异常. 而细心的同学会发现, 在删除集合中的其他元素时都会有并发修改异常发生, 唯独在删除倒数第二个元素的时候不会有异常, 感觉非常的疑惑, 那么在这里跟大家一起分析一下, 这是什么原因.
      首先, 我们先要知道这个并发修改异常是怎么出现的.
在这里我们创建一个集合对象, 并存上几个元素, 为了方便, 我们就存字符串好了:




然后使用迭代器遍历这个集合, 并且在遍历的过程中调用集合的remove方法删除第一个元素:


再看运行结果:


这个时候就出现了并发修改异常!
通过查看java虚拟机给我们显示的异常信息可以看得出来, 发生异常的是ArrayList中的第831行的位置, 我们去查看一下源码:


我们发现, 第831行是迭代器中的next()方法调用了另一个方法checkForComodification(), 然后我们再进去看看这个方法做了什么事:


结果发现, 这个方法只是判断了一下两个变量, 如果这两个变量不相等就抛出并发修改异常, 所以我们想要知道, 这两个变量是代表的什么意思. 这两个变量都是显示为蓝色字体,那很明显它们都是成员变量, 那我们可以到成员位置找一下这两个变量:





在源码中我们找到了这两个变量, 需要注意的是, modCount是AbstractList中的一个成员变量,源码中的解释是, 该变量代表了该集合被修改的次数. 而expectedModCount是Iterator的一个实现类Itr中的一个成员变量, 这个变量在Itr创建对象时, expectedModCount=modCount, 所以expectedModCount也可以看成是集合修改的次数.
         既然这两个变量一开始是相等的, 而在checkForComodification()方法中的判断条件是当expectedModCount!=modCount时抛出并发修改异常, 那么肯定会有某些方法会操作这两个变量, 才会让它们两个不相等, 而我们之前所做的操作就是在遍历的过程中删除元素,所以,我们去看一下集合中的删除方法:


在删除方法里面又调用了fastRemove(index)这个方法, 我们进去看看:


fastRemove(index)这个方法中, 我们发现, 当集合删除一个元素时, modCount就会自增一次, 这个时候expectedModCount却没有发生变化, 结果就会抛出并发修改异常了.
         现在思路已经清晰了, 当迭代器进行遍历时, 集合中的修改次数modCount 会赋值给迭代器中的成员变量expectedModCount , 集合中的删除方法在删除元素时, 会对modCount 进行自增, 而 expectedModCount 却不会变化, 这时迭代器再去调用next() 方法取出元素时, 会先判断expectedModCountmodCount是否相等, 如果不相等就抛出并发修改异常,所以迭代器在遍历的过程中是不能调用集合中的remove方法删除元素的!
         但是问题来了, 为什么在删除集合中的倒数第二个元素的时候不会有问题呢?


这个代码竟然没有出错!!!
这不科学, 不是说在使用迭代器遍历的过程中删除元素会发生并发修改异常的吗?

上面我们已经分析过了, 在迭代器遍历时, 如果调用了集合的remove方法删除元素, 再去调用next()方法时, 就会发生并发修改异常, 而问题就在这里! 在这里我们不得不先说一下另一个方法, 就是迭代器中的hasNext() 方法, 看源码:


源码非常简单, 只要当前的索引跟集合的元素个数不相等, 那么就认为还有元素可以遍历.


cursorItr类中的成员变量, 初始化时并没有赋值, 所以默认值为0, 每遍历一个元素cursor就会跟着自增一次. 当遍历到最后一个元素时, cursor自增就会跟集合的长度相等了,此时迭代器就会认为已经遍历完所有元素了.


看上面的图, 当遍历到集合中的倒数第二个元素时, cursor3, 集合的size5, 然后通过集合中的remove()方法删除d元素, 此时集合的size变成了4, 在遍历下一个元素之前, cursor进行自增,变成了4, 当调用hasNext()方法判断是否有元素时, cursorsize都是4, 所以迭代器就会认为集合中的元素已经遍历完了, 就不会再调用next()方法了, 没有调用到next()方法自然就不会发生并发修改异常了!
          以上就是为什么在迭代器遍历时删除倒数第二个元素不会发生并发修改异常的原因了!

101 个回复

倒序浏览
老师,,   这个问题好像是我提出的, 我也想过说看源码, 但是源码跳过来跳过去的实在是看着痛苦..  你是怎么看的???
回复 使用道具 举报
何亚辉 发表于 2016-6-20 21:01
老师,,   这个问题好像是我提出的, 我也想过说看源码, 但是源码跳过来跳过去的实在是看着痛苦..  你是怎么 ...

按ctrl键点进去看~
回复 使用道具 举报
感谢分享~赞!!!
回复 使用道具 举报

缩噶!!!!!!   
回复 使用道具 举报
何亚辉 发表于 2016-6-20 21:01
老师,,   这个问题好像是我提出的, 我也想过说看源码, 但是源码跳过来跳过去的实在是看着痛苦..  你是怎么 ...

回复 使用道具 举报
good good study,day day up,fighting,fighting,fighting!
回复 使用道具 举报
好好学习,天天向上
回复 使用道具 举报
sxj 中级黑马 2016-6-27 15:38:09
9#
相信自己,加油!
回复 使用道具 举报
可以用ListIterator来避免!
回复 使用道具 举报
DDV 中级黑马 2016-6-27 19:53:59
11#
学习了  好东西
回复 使用道具 举报
新人报道
回复 使用道具 举报
天啊 真是我们的基础班老师吗? 老师我在这啊,完全看不懂啊
回复 使用道具 举报
最后倒计时............
回复 使用道具 举报
学习.....
回复 使用道具 举报
北京修正校区安卓49期的小伙伴有吗,我来签到了
回复 使用道具 举报
odada 中级黑马 2016-6-27 22:22:57
17#
恩,先签个到,再仔细看。。。
回复 使用道具 举报
先签个到再说吧
回复 使用道具 举报
签个到~~~~~
回复 使用道具 举报
日常一签~
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马