0%

多线程的异常处理

多线程的异常处理

在线程执行过程中,若需对异常进行额外处理,可借助 Java 提供的UncaughtExceptionHandler机制。这是线程的一个子接口,当未捕获的异常导致线程中断时,JVM 会通过thread.getUncaughtExceptionHandler()查询线程的异常处理器,并将线程和异常作为参数传递给uncaughtException方法。

UncaughtExceptionHandler 核心原理

UncaughtExceptionHandler是一个函数式接口,其核心定义如下:

1
2
3
4
5
@FunctionalInterface
public interface UncaughtExceptionHandler {
// 由JVM通过thread.dispatchUncaughtException调用,处理未捕获的异常
void uncaughtException(Thread t, Throwable e);
}

异常处理器的传递链

当线程抛出未捕获的异常时,JVM 会按以下顺序查找处理器:

  1. 线程自身的处理器:通过thread.setUncaughtExceptionHandler()设置的实例;
  2. 线程组的处理器:若线程未设置处理器,使用其所属 线程组(ThreadGroup) 的处理器;
  3. 全局默认处理器:若线程组也未设置,使用Thread.setDefaultUncaughtExceptionHandler()设置的全局处理器;
  4. 系统默认行为:若以上均未设置,打印异常堆栈到System.err

线程组(ThreadGroup)默认的异常处理逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e); // 递归到父线程组
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e); // 调用全局处理器
} else if (!(e instanceof ThreadDeath)) {
e.printStackTrace(); // 打印堆栈
}
}
}
}

自定义异常处理器

通过实现UncaughtExceptionHandler接口,可自定义异常处理逻辑(如日志记录、报警、资源释放等)。

步骤 1:实现自定义处理器

1
2
3
4
5
6
7
8
9
class MyExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
// 可添加日志记录、监控报警等逻辑
System.err.println("线程 [" + t.getName() + "] 异常退出:" + e.getMessage());
// 记录完整堆栈到日志文件
Logger.getLogger(getClass()).error("线程异常", e);
}
}

步骤 2:为线程绑定处理器

1
2
3
4
5
6
7
8
9
10
11
public class TestExceptionHandler {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
throw new RuntimeException("测试异常");
});

// 绑定自定义处理器
thread.setUncaughtExceptionHandler(new MyExceptionHandler());
thread.start();
}
}

输出示例

1
2
3
4
5
线程 [Thread-0] 异常退出:测试异常
2023-10-15 14:30:45 ERROR TestExceptionHandler:23 - 线程异常
java.lang.RuntimeException: 测试异常
at TestExceptionHandler.lambda$main$0(TestExceptionHandler.java:10)
...

线程池中的异常处理

实际开发中,线程池是更常用的线程管理方式。但线程池中的异常处理与普通线程存在差异:

  • execute(Runnable):异常会直接抛出,可被UncaughtExceptionHandler捕获;
  • submit(Runnable/Callable):异常会被封装在Future中,需主动调用get()触发。

submit 方法异常捕获机制解析

submit方法将任务包装为FutureTask,其run()方法内部捕获了所有异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// FutureTask源码片段
public void run() {
try {
result = callable.call();
set(result);
} catch (Throwable ex) {
setException(ex); // 存储异常到outcome字段
}
}

// setException方法将异常信息存储在outcome变量中了,而且将state状态修改为了EXCEPTIONAL
protected void setException(Throwable t) {
outcome = t; // 保存异常
state = EXCEPTIONAL; // 标记状态为异常
}

只有调用Future.get()时,异常才会被重新抛出:

1
2
3
4
public V get() throws ExecutionException {
if (state == EXCEPTIONAL)
throw new ExecutionException(outcome); // 包装原始异常
}

线程池异常处理的最佳实践

方式一:任务内部主动捕获异常

在任务逻辑中添加try-catch块:

1
2
3
4
5
6
7
8
9
executorService.submit(() -> {
try {
// 业务逻辑
int result = 1 / 0; // 会触发ArithmeticException
} catch (Exception e) {
// 记录业务异常,避免影响其他任务
logger.error("任务执行失败", e);
}
});
方式二:重写线程池的afterExecute方法

通过钩子方法统一处理异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class CustomThreadPoolExecutor extends ThreadPoolExecutor {
public CustomThreadPoolExecutor(...) {
super(...);
}

@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);

// 处理submit提交的任务异常
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
future.get(); // 触发异常
}
} catch (CancellationException ce) {
// 任务被取消
} catch (ExecutionException ee) {
t = ee.getCause(); // 获取原始异常
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}

// 统一处理所有异常
if (t != null) {
logger.error("线程池任务异常", t);
// 可添加报警逻辑(如发送邮件、短信)
}
}
}
方式三:自定义线程工厂

为线程池中的所有线程设置统一的异常处理器:

1
2
3
4
5
6
7
8
9
10
11
12
ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat("custom-pool-%d")
.setUncaughtExceptionHandler((t, e) -> {
logger.error("线程 [{}] 异常: {}", t.getName(), e.getMessage(), e);
})
.build();

ExecutorService executor = new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
factory
);

全局异常处理器设置

为所有线程设置默认异常处理器:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
// 设置全局异常处理器
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.err.println("全局处理器捕获异常:线程 [" + t.getName() + "] " + e.getMessage());
// 可添加统一日志记录或监控上报
});

// 创建未显式设置处理器的线程
Thread t = new Thread(() -> {
throw new RuntimeException("全局处理器测试");
});
t.start();
}

五、异常处理最佳实践总结

  1. 优先使用线程池:通过ThreadPoolExecutorafterExecute方法统一处理异常;
  2. 任务粒度控制:每个任务应足够独立,避免因单个任务异常导致整个线程池崩溃;
  3. 主动检查 Future 结果:对submit提交的任务,及时调用Future.get()获取结果;
  4. 结合日志与监控:异常发生时,记录完整堆栈信息并触发报警;
  5. 避免静默失败:所有异常都应被明确处理,防止资源泄漏或数据不一致。

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10

域名更新通知

您好!我们的官方域名已更新为 zhhll.com.cn。 请收藏新域名以获取最佳访问体验。