集合的并发修改问题

!!! note 目录

集合的并发修改问题

在 Java 中,集合在迭代时可以通过集合自身的方法(如 remove 方法)或者通过迭代器的 remove 方法来删除元素。这两种删除元素的方法在使用上有明显的区别,尤其是在迭代过程中。

一、集合删除元素和迭代器删除元素的区分

1.1 使用集合方法删除元素

当你在遍历集合时,直接使用集合的 remove 方法来删除元素会导致 ConcurrentModificationException。这是因为集合的 remove 方法会改变集合的结构,而在迭代过程中,修改集合的结构会使迭代器失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 @Test
public void testIteratorRemove(){
//创建集合对象
Collection<String> col = new ArrayList<>();
col.add("hello");
col.add("world");
col.add("java");
//迭代集合,删除集合中的某个元素。
Iterator<String> it = col.iterator();
while (it.hasNext()){
String next = it.next();
if("world".equals(next))col.remove(next); // 会抛出 ConcurrentModificationException
System.out.println(next);
}
}

使用集合删除元素时,迭代器并不知情。

1.2 使用迭代器删除元素

相比之下,使用迭代器的 remove 方法在迭代过程中删除元素是安全的。迭代器维护了一个内部的状态来跟踪集合的结构修改,因此可以正确处理删除操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 @Test
public void testIteratorRemove(){
//创建集合对象
Collection<String> col = new ArrayList<>();
col.add("hello");
col.add("world");
col.add("java");
//迭代集合,删除集合中的某个元素。
Iterator<String> it = col.iterator();
while (it.hasNext()){
String next = it.next();
if(next.equals("world"))it.remove();
System.out.println(next);
}
}

1.3 区别总结

  1. 并发修改检查

    • 集合方法:在迭代过程中使用集合的 remove 方法会导致 ConcurrentModificationException
    • 迭代器方法:迭代器的 remove 方法在迭代过程中删除元素是安全的,因为它处理了并发修改的情况。
  2. 操作便捷性

    • 集合方法:直接调用集合的方法可能更直观和简单,但在迭代过程中不安全。
    • 迭代器方法:使用迭代器的 remove 方法需要额外创建迭代器对象,但它能保证在迭代过程中安全删除元素。
  3. 一致性

    • 集合方法:直接修改集合结构会导致迭代器状态不一致。
    • 迭代器方法:迭代器的 remove 方法确保了迭代器和集合的一致性。

二、fail-fast 机制

fail-fast机制又被称为:快速失败机制。也就是说只要程序发现了程序对集合进行了并发修改。就会立即让其失败,以防出现错误。

2.1 fail-fast 机制如何实现?

  • 集合中设置了一个modCount属性,用来记录修改次数,使用集合对象执行增,删,改中任意一个操作时,modCount就会自动加1。
  • 获取迭代器对象的时候,会给迭代器对象初始化一个expectedModCount属性。并且将expectedModCount初始化为modCount,即:int expectedModCount = modCount;
  • 当使用集合对象删除元素时:modCount会加1。但是迭代器中的expectedModCount不会加1。而当迭代器对象的next()方法执行时,会检测expectedModCountmodCount是否相等,如果不相等,则抛出:ConcurrentModificationException异常。
  • 当使用迭代器删除元素的时候:modCount会加1,并且expectedModCount也会加1。这样当迭代器对象的next()方法执行时,检测到的expectedModCount和modCount相等,则不会出现ConcurrentModificationException异常。

注意:

  • 虽然我们当前写的程序是单线程的程序,并没有使用多线程,但是通过迭代器去遍历的同时使用集合去删除元素,这个行为将被认定为并发修改。
  • 迭代器的remove()方法删除的是next()方法的返回的那个数据。remove()方法调用之前一定是先调用了next()方法,如果不是这样的,就会报错。