快速失败(fail-fast)和安全失败(fail-safe)的区别
java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
快速失败示例
public class FailFastTest { //(01) 新建了一个ArrayList,名称为arrayList。 private static List<Integer> list = new ArrayList<>(); public static void main(String[] args) { //(02) 向arrayList中添加内容 for(int i = 0 ; i < 10;i++){ list.add(i); } //(03) 新建一个“线程a”,并在“线程a”中通过Iterator反复的读取arrayList的值。 new threadOne().start(); //(04) 新建一个“线程b”,在“线程b”中删除arrayList中的一个“节点A”。 new threadTwo().start(); } /** * @desc:线程one迭代list */ private static class threadOne extends Thread{ public void run() { Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ int i = iterator.next(); System.out.println("ThreadOne 遍历:" + i); } } } /** * @desc:线程two修改list */ private static class threadTwo extends Thread{ public void run(){ int i = 0 ; while(i < 6){ System.out.println("ThreadTwo run:" + i); if(i == 2){ list.remove(i); } i++; } } } }
fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。例如用CopyOnWriteArrayList 来代替ArrayList。
fail-fast产生原因: 在“线程a”在遍历arrayList过程中的某一时刻,“线程b”执行了,并且“线程b”删除了arrayList中的“节点A”。“线程b”执行remove()进行删除操作时,在remove()中执行了“modCount++”,此时modCount变成了N+1!
“线程a”接着遍历,当它执行到next()函数时,调用checkForComodification()比较“expectedModCount”和“modCount”的大小;而“expectedModCount=N”,“modCount=N+1”,这样,便抛出ConcurrentModificationException异常,产生fail-fast事件。
从中,我们可以看出:
(01) ArrayList继承于AbstractList,而CopyOnWriteArrayList没有继承于AbstractList,仅仅只是实现了List接口。
(02) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。
(03)
ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常!