MST
星途 面试题库

面试题:Java迭代器模式中fail - fast机制在多线程环境下的应用与原理

在多线程环境下,Java的迭代器有fail - fast机制。请解释fail - fast机制的原理是什么?如果在一个多线程程序中,多个线程同时访问并修改一个使用迭代器遍历的集合,会发生什么情况?如何通过代码示例展示这种情况,并说明如何避免因fail - fast机制导致的意外异常。
26.2万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

fail - fast机制的原理

在Java中,当使用迭代器遍历集合时,迭代器会维护一个内部的modCount变量,该变量记录了集合被修改的次数。每次集合结构发生改变(如添加、删除元素),集合内部的modCount会自增。当迭代器的next()remove()方法被调用时,迭代器会检查自身的expectedModCount与集合的modCount是否相等。如果不相等,就抛出ConcurrentModificationException,这就是fail - fast机制,它用于快速检测到集合在迭代过程中被意外修改。

多线程同时访问并修改集合的情况

如果在多线程程序中,多个线程同时访问并修改一个使用迭代器遍历的集合,由于不同线程对集合的修改会导致modCount的变化,迭代器可能会检测到这种不一致,从而抛出ConcurrentModificationException异常,导致遍历失败。

代码示例展示这种情况

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class FailFastExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        Thread thread1 = new Thread(() -> {
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println("Thread 1: " + element);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.add("D");
            System.out.println("Thread 2 added element D");
        });

        thread1.start();
        thread2.start();
    }
}

在上述代码中,thread1尝试遍历列表,thread2thread1遍历过程中向列表添加元素。运行代码时,thread1很可能在遍历过程中抛出ConcurrentModificationException

避免因fail - fast机制导致的意外异常

  1. 使用线程安全的集合:例如CopyOnWriteArrayList,它在修改集合时会创建一个新的底层数组,迭代器遍历的是旧的数组,所以不会抛出ConcurrentModificationException
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class AvoidFailFastExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        Thread thread1 = new Thread(() -> {
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println("Thread 1: " + element);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.add("D");
            System.out.println("Thread 2 added element D");
        });

        thread1.start();
        thread2.start();
    }
}
  1. 使用同步块:在遍历和修改集合时使用synchronized关键字同步访问集合,确保同一时间只有一个线程能修改集合。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class SynchronizedExample {
    private static final List<String> list = new ArrayList<>();

    static {
        list.add("A");
        list.add("B");
        list.add("C");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (list) {
                Iterator<String> iterator = list.iterator();
                while (iterator.hasNext()) {
                    String element = iterator.next();
                    System.out.println("Thread 1: " + element);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (list) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("D");
                System.out.println("Thread 2 added element D");
            }
        });

        thread1.start();
        thread2.start();
    }
}