fail-fast机制(快速失败):
ConcurrentModificationException发生在Collection集合使用迭代器遍历时,使用了集合类提供的修改(增加、删除)集合内容方法报错,或者由于多线程操作导致。
1.单线程情况
- modCount表示当前集合修改的次数,也可以认为是实际对象的个数。
- ecpectedModCount是迭代器记录当前集合的修改次数。
1.1抛出上述异常的主要原因:
当调用容器的iterator()
方法返回Iterator对象时,把容器中包含对象的个数赋值给了一个变量(ecpectedModCount = modCount),在调用next()
方法时会比较ecpectedModCount 与modCount的值是否相等,若二者不相等,则会抛出该异常。
1.2解决方法:
在遍历过程中把需要删除的对象保存到一个集合中,等遍历结束后再调用removeAll()
方法来删除,或者使用iterator.remove()
方法。
1.3意义:
快速失败策略保证了所有用户在进行迭代遍历集合时,拿到的数据一定时最新的数据。(避免“脏读”产生)
1.4注意:
这里异常的抛出条件是检测到ecpectedModCount != modCount这个条件。如果集合发生变化时修改modCount值刚好又设置了ecpectedModCount值,则异常不会抛出。因此,不能依赖这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
1.5场景:
Java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
2.多线程情况
2.1解决方法:
- a. 在JDK1.5版本引入线程安全的容器,比如ConcurrentHashMap和CopyOnWriteArrayList等。可以使用这些线程安全的容器来代替非线程安全的容器。
- b. 在使用迭代器遍历容器时对容器的操作放到sychronized代码块中,但是当引用程序并发程度比较高时,这会严重影响程序的性能。
fail-safe(安全失败):
1.原理:
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。
2.缺点:
基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。(我认为这是一项掩耳盗铃的操作)
3.场景:
java.util.concurrent包下的容器都是安全失败,可以在多个线程并发下使用,并发修改。
4.总结:
以后在迭代器遍历时不要修改集合内容