我们由简到繁来叙述这件事
集合中删除元素报错
这个是很很基础但每一个程序员开发之路上都会遇到的报错,即ConcurrentModificationException
- 现象:在加强for循环中,使用集合本身的方法去删除了某个元素,比如
for (String obj: list) {if (obj.equals("target")) {list.remove(obj);}
}
就会报ConcurrentModificationException
- 原因:因为加强for循环中,元素的遍历其实是用的迭代器Iterator,删除的时候用的却是集合本身的方法,就会导致Iterator在遍历的过程中发现元素数目对不上了,所以会在iterator.next()这一行报错,告诉系统modCount比expectedModCount小了,两者不一致,所以出现ConcurrentModificationException。
- 解决办法: 既然知道了原因,解决办法也很简单也很多,比如
- 遍历和删除都用Iterator即可(都用fori循环也不会报错,但是会出现错删漏删的情况)
- 使用java.stream.filter
- 使用一些fail-safe安全的类,比如ConcurrentLinkedDeque
fail-fast和fail-safe机制
通常所指的failfast(快速失败)和failsafe(安全失败)就是java集合类中对并发修改检测的一种安全机制,但其实它是一种通用的软件设计原则,可以用于多种语言,只不过可能java的更广为人知罢了。
fail-fast:在遍历过程中,如果发现容器中的元素被修改了(也就是modCount!=expectedModCount),那么就会出现快速失败的报错来中止这个过程,来防止更为严重后果。
多线程并发修改某个共享集合可能会导致这种情况,同理上述的单线程中,遍历和操作用的不是一个机制的时候,也会导致同样的场景。
fail-safe:如果不想抛出这种错误,那么可以使用fail-save安全的类,比如java.concurrent下面的类,就没有modCount!=expectedModCount的监测,对单线程来讲,可以毫无顾忌的使用。
对于多线程来讲,因为这种类一般是每个线程创建了一个副本,在副本上修改,来避免这种报错的发生,就会导致原有集合发生变化的时候,副本是无感知的。