关于快速失败机制的一些思考

前言

本文是写在本人在网上找了很多资料也没有找到关于 “如何正确应对ConcurrentModificationException这个异常的” 的前提, 本文均是作者自己的思考, 如有错误欢迎指出

开始

先决条件: 对java集合的fail-fast有一定了解

我们都知道, 在进行增强for循环迭代时, 不能直接用集合的remove方法, 这样会抛出java.util.ConcurrentModificationException

比如:

List l = new LinkedList<String>(){{
	add("b");
	add("b");
	add("c");
}};

for (String s : l) {
	if ("b".equals(s)) {
		l.remove(s);
	}
}

这样就会抛出异常, 正确的姿势是

Iterator<String> itr = l.iterator();
while (itr.hashNext() {
	String s = itr.next();
	if ("b".equals(s)) {
		itr.remove(); // 利用迭代器移除元素
	}
}

其他不在赘述, 具体源码分析推荐 这一篇博文

今天主要讨论在多线程环境下的快速失败机制及其作用

fail-fast定义

Definition of Failing Fast:

To fail fast means to have a process of starting work on a project, immediately gathering feedback, and then determining whether to continue working on that task or take a different approach—that is, adapt. If a project is not working, it is best to determine that early on in the process rather than waiting until too much money and time has been spent.

-- 机翻

快速失败意味着有一个开始项目工作的过程,立即收集反馈,然后决定是继续工作还是采取不同的方法ーー也就是适应。如果一个项目不起作用,最好在过程的早期就确定,而不要等到花费了太多的金钱和时间。

总的来说这是一个设计思想, 而非java中独有的东西, 目的是快速反馈, 然后再决策. 在java中, 这个反馈就是ConcurrentModificationException这个异常

例子

LinkedList并非一个线程安全的集合

假设有这样一个场景:

线程一中正在利用集合的迭代器, 线程二正在修改这个集合

那么线程一就有可能会发生意想不到的问题

下面的代码例子中list初始化为["a", "b", "c"]

然后新建一个线程, 在此线程中利用list的迭代器来进行迭代打印的操作, 并且迭代到"b"这一项会导致此线程休眠一秒

主线程首先会休眠0.5秒, 然后修改集合, 添加一个元素"d", 然后打印集合

package test;  
  
import java.util.Iterator;  
import java.util.LinkedList;  
import java.util.List;  
  
public class TestThread {  
    public static void main(String[] args) throws InterruptedException {  
  
        LinkedList<String> list = new LinkedList<>(){{  
            add("a");  
            add("b");  
            add("c");  
        }};  
  
        new Thread(() -> {  
            Iterator<String> itr = list.iterator();  
  
            while (itr.hasNext()) {  
                String s = itr.next();  
                if ("b".equals(s)) {  
                    try {  
                        Thread.sleep(1000);  
                    } catch (InterruptedException e) {  
                        throw new RuntimeException(e);  
                    }  
                }  
                System.out.println(s);  
            }  
        }).start();  
  
        Thread.sleep(500);  // 由于迭代速度比添加元素快, 所以这里要让主线程休眠一下
        list.add("d");  
        System.out.println(list);  
  
    }  
}

请添加图片描述

嗯… 不出意外抛了ConcurrentModificationException

之前看到, 利用迭代器的remove()方法可以对expectedModCount重新赋值, 下面用一个不严谨不客观的测试看看这个能不能避免异常

if ("b".equals(s)) {  
    try {  
        Thread.sleep(1000);  
    } catch (InterruptedException e) {  
        throw new RuntimeException(e);  
    }  
    itr.remove();  // 改动在这里
}

请添加图片描述

是的, 不可以避免, 并且这个异常被提前抛出了, 即在itr.remove()的时候就抛出了

但是为什么要抛出这样一个异常?

我想说下自己的想法:

首先我们想一下, 在对LinkedList集合进行增删之后继续用原来的iterator遍历是否合理?

私以为, 不合理, 他会产生逻辑上的错误, 比如有这样一个场景, LinkedList的一个集合存放了账户交易账单(接下来简称list), 接下来会对这些集合的所有元素进行迭代处理, 比如发短信 blabal…

然而迭代处理这个list和添加这些list的元素在操作两个不同的线程, 如果在迭代处理list的过程中另一个线程又添加了元素, 那么不就会发生业务bug了吗? (比如有用户没收到短信)

ConcurrentModificationException这个异常用来告知我们集合发生了变更

我们也可以处理这个异常, 比如可以捕获这个异常, 重新迭代

Iterator<String> itr = list.iterator();  
while (itr.hasNext()) {  
    try {  
        String s = itr.next();  
        if ("b".equals(s)) {  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
            itr.remove();  
        }  
        System.out.println(s);  
    } catch (ConcurrentModificationException e) {  // 捕获这个异常, 重新迭代
        itr = list.iterator();  
    }  
}

请添加图片描述

当然这只是众多操作的一种

但是, 我认为我们不能依赖ConcurrentModificationException, LinkedList终究是线程不安全的集合, 多线程操作下我们还是需要选择线程安全集合, 如CopyOnWriteArrayListVector之类

假设我们把这里的LinkedList换成CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList<>(){{  
    add("a");  
    add("b");  
    add("c");  
}}; // 用这个集合后就不会抛出异常了!

请添加图片描述

最后

我认为ConcurrentModificationException异常作用应该仅仅当作 “异常的操作”, 即告诉程序员们这样用是错误的, 会导致逻辑错误

当然决策权还是在程序员们, 到底怎么处理还应看实际情况

如有错误, 欢迎指正!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值