fail-fast 机制

fail-fast 机制

fail-fast 机制是集合里比较常见的错误检测机制,通常出现在遍历集合元素的过程中。

它是对集合遍历操作时的错误检测机制,在遍历中途出现意料之外的修改时,通过 unchecked 异常直接将错误给抛出来。这种机制经常出现在多线程环境下,当前线程会维护一个计数比较器,即 exceptedModCount,记录已经修改的次数。在进入遍历前,会把实时修改次数 modCount 赋值给 exceptedModCount,如果这两个数据不相等,则抛出 ConcurrentModificationException 异常。java.util 下的所有集合类都是 fail-fast的,而 concurrent 包中的集合类都是 fail-safe 。 与 fail-fast 不同,fail-safe 是在安全的副本(拷贝原先的集合)上进行遍历,集合修改与副本的遍历是没有任何关系的,因此不会抛出 ConcurrentModificationException 异常。但是缺点也很明显,就是读取不到最新的数据。

下面通过 ArrayList.subList() 方法进一步阐述 fail-fast 这种机制。在某种情况下,需要从一个主列表 masterList中获取子列表 branchList,masterList 集合元素个数的增加或删除,均会导致子列表的遍历、增加、删除,进而会触发 fail-fast 机制。

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);
//  以下三行代码,如果不注释掉,则会导致 subList 操作出现异常(第1处)
masterList.remove(0);
masterList.add("ten");
masterList.clear();

branchList.clear();
branchList.add("six");
branchList.add("seven");
branchList.remove(0);
for (Object o : branchList) {
    System.out.println(o);  // 输出 seven
}
System.out.println(masterList); // 输出 [seven, four, five]

第1处说明,如果不注释掉,masterList 的任何关于元素个数的修改操作都会导致 branchList 的 “增删查改” 操作出现 ConcurrentModificationException 异常。branchList 子列表的修改会导致主列表的修改,但是不知道主列表元素个数的改动会让子列表如此敏感,频频抛出异常。

我们可以使用 Iterator 机制进行遍历时的删除,如果是多线程并发,还需要在 Iterator 遍历时加锁,如下示例代码:

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    synchronized(this) {
        String item = iterator.next();
        if (删除元素的条件) {
            iterator.remove();
        }
    }
}

或者使用并发容器 CopyOnWriteArrayList 代替 ArrayList,即 Copy-On-Write。是一种并发的新思路,能够实现读写分离,如果是写操作,则复制一个新集合,在新集合内添加或删除元素。待一切修改完成之后,再将原集合的引用指向新的集合。这样做的好处是可以高并发地对 CopyOnWriteArrayList 进行读和遍历操作,而不需要加锁,因为当前集合的元素不会发生任何改动。但是,在使用 CopyOnWriteArrayList 时,需要留意两点的是:一是尽量设置合理的容量初始值,因为它扩容的代价比较大;二是使用批量添加或删除方法,如 addAll 或 removeAll 操作,在高并发请求下,可以积累一些要添加或删除的元素,避免出现增加/删除一个元素而复制整个集合,避免没必要的性能开销。

如果集合数据是 100 MB,再写入 50 MB,那么某个时间段内占用的内存就达到(100MB * 2) + 50 MB = 250 MB,内存的大量占用就会导致 GC 的频繁发生,从而降低服务器的性能。笔者曾用代码测试过,分别在 CopyOnWriteArrayList 和 ArrayList 中分别添加 20 w 条数据,CopyOnWriteArrayList 就要花费 100 秒左右,而 ArrayList 只需花费 50 毫秒左右。因此,我们可以将数据先填充到 ArrayList 集合中,然后再将 ArrayList 集合当成 CopyOnWriteArrayList 的参数,这就是使用批量添加的另一种方式。由此可以看出 CopyOnWriteArrayList 适用于 读多写少的场景。

本文章参考自《码出高效》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值