的变形Collection通过,尽管迭代Collection使用Iterator被不允许被大多数的Collection类。Java库将Collection在迭代过程中进行修改的尝试称为“并行修改”。不幸的是,这表明唯一可能的原因是多个线程同时进行了修改,但事实并非如此。仅使用一个线程就可以为Collection(使用Collection.iterator()或增强的for循环)创建一个迭代器,开始进行迭代(使用Iterator.next()或等效地进入增强的for循环的主体),修改Collection,然后继续进行迭代。
为了帮助程序员,这些类的某些实现尝试检测错误的并发修改,并在检测到错误时抛出。但是,通常不可能保证检测所有并发的修改。因此,错误地使用并不总是会导致抛出错误。CollectionConcurrentModificationExceptionCollectionConcurrentModificationException
的文档ConcurrentModificationException说:
当不允许对对象进行并发修改时,检测到该对象的并发修改的方法可能会引发此异常。
请注意,此异常并不总是表示对象已被其他线程同时修改。如果单个线程发出违反对象约定的方法调用序列,则对象可能会抛出此异常...
请注意,不能保证快速故障行为,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。快速失败的操作ConcurrentModificationException是尽力而为的。
注意
的文档HashSet,HashMap,TreeSet和ArrayList类这样说:
从该类直接或间接返回的迭代器是快速失败的:如果在创建迭代器后的任何时间修改[collection],则除了通过迭代器自己的remove方法之外,都以任何方式对其进行Iterator抛出ConcurrentModificationException。因此,面对并发修改,迭代器会快速干净地失败,而不会在未来的不确定时间冒着任意,不确定的行为的风险。
注意,迭代器的快速失败行为无法得到保证,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。快速失败的迭代器会ConcurrentModificationException尽力而为。因此,编写依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为应仅用于检测错误。
再次注意,行为“无法保证”,而仅仅是“尽力而为”。
Map接口的几种方法的文档说:
非并行实现应重写此方法,并在尽力而为的基础上,ConcurrentModificationException如果在计算过程中检测到映射函数修改了此映射,则抛出。并发实现应重写此方法,并且在尽力而为的基础上,IllegalStateException如果检测到映射函数在计算过程中修改了此映射,则抛出该错误,因此计算将永远不会完成。
再次注意,检测仅需要“尽力而为”,并且ConcurrentModificationException仅针对非并发(非线程安全)类明确建议使用a 。
调试 ConcurrentModificationException
因此,当您看到由于a引起的堆栈跟踪时ConcurrentModificationException,您不能立即假定原因是对a的不安全多线程访问Collection。您必须检查堆栈跟踪以确定哪个类Collection引发了异常(该类的方法将直接或间接引发该异常),以及针对哪个Collection对象。然后,您必须检查可从何处修改该对象。
编程以防止并发修改错误
在可能的情况下,将所有引用限制在一个Collection对象上,以便更容易防止并发修改。使Collection一个private物体或一个局部变量,也不要到返回引用Collection的方法或它的迭代器。这样一来,检查可以修改的所有地方就容易得多Collection。如果Collection要由多个线程使用,则可以确保Collection通过适当的同步和锁定来仅访问线程。