在Java多线程开发中,线程执行过程中抛出的异常如果未被正确处理,不会像普通方法调用那样直接抛出到调用方,而是可能导致线程直接终止,甚至影响整个程序的稳定性。因此掌握线程执行异常的处理方式是Java并发开发的基础技能。

直接在run方法内捕获异常
最基础的处理方式是在Runnable或Callable的run方法内部使用try-catch块捕获异常,这种方式适合异常逻辑简单、仅需要在当前线程内处理的场景。
public class ThreadInnerCatchDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
// 模拟可能抛出异常的代码
int result = 10 / 0;
System.out.println("计算结果:" + result);
} catch (Exception e) {
System.err.println("线程内捕获到异常:" + e.getMessage());
// 这里可以添加异常恢复、日志记录等逻辑
}
});
thread.start();
}
}
这种方式的局限性在于异常只能在当前线程内部处理,无法将异常信息传递到线程外部,如果需要在外部感知线程执行状态就不太适用。
使用UncaughtExceptionHandler接口
Java提供了UncaughtExceptionHandler接口,用于在线程因未捕获异常而终止时,回调处理异常逻辑。我们可以为线程设置自定义的异常处理器,实现异常的统一处理。
为单个线程设置异常处理器
public class UncaughtExceptionHandlerDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
// 这里不捕获异常,让异常抛出
int result = 10 / 0;
System.out.println("计算结果:" + result);
});
// 设置自定义的未捕获异常处理器
thread.setUncaughtExceptionHandler((t, e) -> {
System.err.println("线程" + t.getName() + "抛出未捕获异常:" + e.getMessage());
// 可以添加全局日志记录、告警等逻辑
});
thread.start();
}
}
设置线程默认的异常处理器
如果希望所有线程都使用同一个异常处理器,可以调用Thread.setDefaultUncaughtExceptionHandler方法设置默认处理器,后续创建的所有线程如果没有单独设置处理器,都会使用这个默认配置。
public class DefaultExceptionHandlerDemo {
public static void main(String[] args) {
// 设置所有线程的默认未捕获异常处理器
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.err.println("默认处理器捕获线程" + t.getName() + "的异常:" + e.getMessage());
});
// 创建两个线程测试
new Thread(() -> {
int a = 10 / 0;
}, "thread-1").start();
new Thread(() -> {
String s = null;
s.length();
}, "thread-2").start();
}
}
线程池场景下的异常处理
如果使用线程池提交任务,异常的处理方式会有所不同,需要根据提交任务的方式选择对应的处理方案。
提交Callable任务获取异常
当使用submit方法提交Callable任务时,任务抛出的异常会被封装到返回的Future对象中,调用Future.get()方法时会重新抛出,我们可以通过捕获ExecutionException来获取原始异常。
import java.util.concurrent.*;
public class ThreadPoolCallableDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
// 任务中抛出异常
return 10 / 0;
});
try {
Integer result = future.get();
System.out.println("任务结果:" + result);
} catch (InterruptedException e) {
System.err.println("线程被中断:" + e.getMessage());
} catch (ExecutionException e) {
// 通过ExecutionException获取原始异常
System.err.println("任务执行异常:" + e.getCause().getMessage());
} finally {
executor.shutdown();
}
}
}
提交Runnable任务的处理方式
如果使用submit提交Runnable任务,异常同样会被封装到Future中,调用get方法时可以获取。如果使用execute方法提交Runnable任务,未捕获的异常会触发线程的UncaughtExceptionHandler,因此可以为线程池的线程设置异常处理器来处理。
import java.util.concurrent.*;
public class ThreadPoolRunnableDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r);
// 为线程池的工作线程设置异常处理器
t.setUncaughtExceptionHandler((thread, e) -> {
System.err.println("线程池线程" + thread.getName() + "异常:" + e.getMessage());
});
return t;
});
// 使用execute提交Runnable任务
executor.execute(() -> {
int result = 10 / 0;
});
executor.shutdown();
}
}
不同处理方式的适用场景对比
为了更清晰地选择处理方式,我们可以参考以下场景对比:
| 处理方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| run方法内捕获 | 异常仅需在当前线程内处理,不需要外部感知 | 实现简单,逻辑直观 | 无法将异常传递到线程外部 |
| UncaughtExceptionHandler | 需要统一处理线程未捕获异常,或者做全局日志、告警 | 可以统一处理所有未捕获异常,解耦异常处理逻辑 | 只能处理未捕获的异常,已捕获的异常不会触发 |
| 线程池Future获取异常 | 使用线程池提交任务,需要获取任务执行结果和异常 | 可以明确获取任务执行状态和异常信息 | 需要主动调用get方法获取,会阻塞当前线程 |
在实际开发中,我们可以根据具体的业务场景选择合适的异常处理方式,也可以组合使用多种方案,比如在线程内捕获可恢复的异常,用UncaughtExceptionHandler处理未预期的异常,保证程序的健壮性。