本文共 3119 字,大约阅读时间需要 10 分钟。
本篇参考 《码出高效 Java 开发手册》
fail-fast
机制是集合世界中比较常见的错误检测机制,通常出现在遍历集合元素的过程中,下面通过校园生活中的一个例子来体会 fail-fast
机制。fail-fast
机制。它是一种对集合遍历操作时的错误检测机制,在遍历中途出现意料之外的修改时,通过 unchecked
异常暴力的反馈出来,这种机制经常出现在多线程环境下,当前线程会维护一个计数比较器,叫做 expectedModCount
,记录已经修改过的次数,在进入遍历前,会把实时修改次数 modCount
赋值给 expectedModCount
,如果这两个数据不相等,则抛出异常。java.util 下的所有集合类都是 fail-fast
,而 concurrent 包中的集合类都是 fail-safe
。与 fail-fast
不同,fail-safe
对于刚才点名被频繁打断的情形,相当于班长直接拿出手机快速拍照,然后跟据照片点名,不在关心同学们的进进出出。ArrayList.subList()
方法进一步阐述fail-fast
这种机制,在某种情况下,需要从一个主列表 master 中获取子列表 branch,master 集合元素个数的增加或删除,均会导致子列表的遍历、增加、删除进而产生 fail-fast
异常,代码分析如下:@Test public void test01() { List masterList = new ArrayList(); masterList.add("one"); masterList.add("two"); masterList.add("three"); masterList.add("four"); masterList.add("five"); List branchList = masterList.subList(0, 3); System.out.println(branchList); // 下方三行代码,如果不注释掉,则会导致 branchList 操作出现异常 (`第一处`) masterList.remove(0); masterList.add("ten"); masterList.clear(); // 下方四行全部执行成功 branchList.clear(); branchList.add("six"); branchList.add("seven"); branchList.remove(0); // 正常遍历结束 :只有一个元素:seven for (Object t : branchList) { System.out.println(t); } // 子列表修改导致主列表也被动修改,输出:[seven, four, five] System.out.println(masterList); }
ConcurrentModificationException
异常,在实际调研中,大部分程序员知道 subList 子列表无法序列化,也知道它的修改会导致主列表的修改,但是并不知道主列表个数的改动会让子列表如此敏感,频频抛出异常,在实际代码中,这样的故障案例属于常见的类型, subList 方法返回的是内部类 SubList 的对象,SubList 类是 ArrayList 的内部类,SubList 的定义如下,并没有实现序列化接口,无法网络传输:private static class SubList<E> extends AbstractList<E> implements RandomAccess {...}
fail-fast
机制,查看如下代码:@Test public void test02() { Listlist = new ArrayList (); list.add("one"); list.add("two"); list.add("three"); for (String s : list) { if ("two".equals(s)) { list.remove(s); } } System.out.println(list); }
ConcurrentModificationException
异常呢?这只是一种巧合,在集合遍历时维护一个初始值为0的游标 cursor
,从头到尾的进行扫描,当 cursor
等于 size 时,退出遍历,执行 remove
这个元素后,所有元素往前拷贝,size = size -1
即为2,这时 cursor 也等于2。在执行 hasNext()
时,结果为 false,退出循环体,并没有机会执行到 next()
的第一行代码 checkForComodification()
,此方法用来判断 expectedModCount 和 modCount 是否相等,如果不相等,则会抛出 ConcurrentModificationException 异常。Iteratoriterator = list.iterator();while(iterator.hasNext()){ synchronized(对象) { String item = iterator.next(); if (删除元素的条件) { iterator.remove(); } }}
CopyOnWriteArrayList
代替 ArrayList。顺便介绍一个 COW 奶牛家族,即 Copy-On-Write。它是并发的一种新思路,实行读写分离,如果要是写操作,则复制一个新集合,在新集合中对元素进行添加或删除,待一切都修改完成后,再将原集合的引用指向新集合,这样做的好处是可与高并发的对 COW 进行读和遍历操作,而不需要加锁,因为当前集合不会添加任何元素,使用 COW 时应该注意两点: 尽量设置合理的容量初始值,它扩容的代价比较大;
使用批量添加或删除方法,如 addAll 或 removeAll 操作,在高并发请求下,可以攒一下要添加或者删除的元素,避免增加一个元素复制整个集合。
fail-safe
机制的,在并发包中的集合都是由这种机制实现的,fail-safe 是在安全的副本(或者说没有修改操作的正本)上进行遍历,集合修改与副本的遍历是没有任何关系的,但是缺点也很明显,就是读取不到最新的数据
。这也是 CAP 理论中 一致性和可用性的矛盾之处。转载地址:http://nsqwi.baihongyu.com/