捕获多线程异常,我经历了哪几步?

277 阅读2分钟

前言:

先思考一个问题,为什么非要异常一定抛出给外层让外层进行捕获?因为外层有统一的异常处理逻辑,比如本文示例中业务流程中的审批失败发送告警通知。我们有时候希望对整个程序的异常处理都是用同一套异常处理方案,那么内部异常就一定要抛出给外层。

背景:

在工作中遇到一个关于多线程异常的问题,业务流程是在审批处理接口中有个多线程上线产品的操作,在程序运行过程中有用户反馈审批成功,但是产品没有上线,结果看日志排查发现其实在调用上线产品操作时是失败的,但是整个审批处理是成功的,没有异常,也没有发送告警通知。审批处理的伪代码如下:

// 审批处理
@Transactional(rollbackFor = Exception.class)
public void handleApproval() {
    try {
        if (agree) {
            // 更新审批状态
            // 上线产品:onLineProduct();
        } else {
            // 更新审批状态
        }
    } catch (Exception e) {
        // 审批出现错误:打印log
        // 发送告警通知:sendAlarm()
    } finally {
        // 耗时统计及打印
    }
}

在上线产品onLineProduct()方法中,原伪代码如下:

private void onLineProduct() {
    CountDownLatch countDownLatch = new CountDownLatch(sumCount);
    for (sumCount) {
        Runnable onlineTask = () -> {
            try {
                //上线
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                countDownLatch.countDown();
            }
        };
        threadPool.submit(onlineTask);
    }

    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        throw new Exception("err");
    }
}

那么到底如何捕获多线程异常呢?123走起:

1、尝试throw 出异常,对onLineProduct()伪代码进行修改,核心逻辑如下:

for (sumCount) {
    Runnable onlineTask = () -> {
        try {
            //上线
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new Exception("上线err");
        } finally {
            countDownLatch.countDown();
        }
    };
    threadPool.submit(onlineTask);
}

结论:没有什么用,没有生效,上线失败后外层依然捕获不到,同样没有告警通知,百度搜索说多线程里面的异常外层无法捕获,因为是独立的。

2、简单粗暴,直接取消多线程,改成正常按顺序执行,有异常一定会被捕获到,也会发送告警通知。没毛病,但是万一确实需要多线程调用呢?

3、使用Callable处理多线程,Callable可以将异常抛出到调用线程,亲测有效,伪代码如下:

for (sumCount) {
	//Callable可以将异常抛出到调用线程,参考文章:https://www.codenong.com/12305667/
	Callable task = () -> {
		//上线
		return null;
	};
	Future future = fixedThreadPool.submit(task);
	try {
		future.get();
	} catch (Exception e) {
		log.error(e.getMessage(), e);
		throw new Exception("上线err");
	} finally {
		countDownLatch.countDown();
	}
}