@Test
void ThreadTest() {
System.out.println("aaaaaaaaaaa");
Thread t = new Thread(() -> System.out.println(1 / 0));
t.start();
System.out.println("bbbbbbbbbbbbbbbbb");
}
如果我们执行上面这段代码,会在控制台上看到异常输出。 在Java中,线程中的异常是不能抛出到调用该线程的外部方法中捕获的,只能在线程内部进行捕获。
为什么不能抛出异常到外部线程捕获?
因为线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。基于这样的设计理念,在Java中,线程方法的异常都应该在线程代码边界之内(run方法内)进行处理。换句话说,我们不能捕获从线程中逃逸的异常。
JDK如何控制线程异常不会跑到外部线程?
通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throw exception部分)进行了约束。
如果在线程中抛出异常线程会怎么样?
线程会立即终结。
aaaaaaaaaaa
bbbbbbbbbbbbbbbbb
Exception in thread "Thread-544" java.lang.ArithmeticException: / by zero
at
通常情况下绝大多数线上应用不会将控制台作为日志输出地址,而是另有日志输出。这种情况下,上面的代码所抛出异常便会丢失。那为了将异常输出到日志中,我们会这样写代码:
@Test
void ThreadTest() {
System.out.println("aaaaaaaaaaa");
Thread t = new Thread(() -> {
try{
System.out.println(1 / 0);
}catch (Exception e){
e.printStackTrace();
log.error(e.getMessage());
}
}
);
t.start();
System.out.println("bbbbbbbbbbbbbbbbb");
}
aaaaaaaaaaa
bbbbbbbbbbbbbbbbb
2022-04-13 16:19:43.338 ERROR 15984 --- [ Thread-544] ERROR : / by zero
这样我们就能异常栈输出到日志中,而不是控制台,从而避免异常的丢失。可能好多线程任务默认的异常处理机制都是相同的。比如都是将异常输出到日志文件。按照上面的写法会造成重复代码。那我们该如何解决这个问题呢?
Thread 类中有个接口 UncaughtExceptionHandler,
使用全局异常处理 UncaughtExceptionHandler
UncaughtExceptionHandler能检测出某个线程由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器(这是Thread类中的接口):
public interface UncaughtExceptionHanlder {
void uncaughtException(Thread t, Throwable e);
}
JDK5之后可以在每一个Thread对象上添加一个异常处理器UncaughtExceptionHandler 。Thread.UncaughtExceptionHandler.uncaughtException()方法会在线程因未捕获的异常而面临死亡时被调用。
我们通过 Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler) 设置全局的默认异常处理机制。
@Test
void ThreadTest() {
System.out.println("aaaaaaaaaaa");
Thread t = new Thread(() -> {
System.out.println(1 / 0);
System.out.println("ccccccccccccc");
}
);
t.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
t.start();
System.out.println("bbbbbbbbbbbbbbbbb");
}
class MyUncheckedExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获到异常。异常栈信息为:"+e.getMessage());
e.printStackTrace();
}
}
@Test
void ThreadTest() {
System.out.println("aaaaaaaaaaa");
ThreadGroup threadGroup = new ThreadGroup("MyGroup") {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("捕获到异常。异常栈信息为:"+e.getMessage());
e.printStackTrace();
}
};
Thread t = new Thread(threadGroup, () -> {
System.out.println(1 / 0);
System.out.println("ccccccccccccc");
}
);
// t.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
t.start();
System.out.println("bbbbbbbbbbbbbbbbb");
}
aaaaaaaaaaa
bbbbbbbbbbbbbbbbb
2022-04-13 16:45:27.482 ERROR 87200 — [ Thread-544] ERROR Test : 捕获到异常。异常栈信息为:/ by zero
java.lang.ArithmeticException: / by zero
at
如果只需要一个线程异常处理器处理线程的异常,那么我们可以设置一个默认的线程异常处理器,当线程出现异常时,
如果没有指定线程的异常处理器,而且线程组也没有设置,那么就会使用默认的线程异常处理器
@Test
void ThreadTest() {
System.out.println("aaaaaaaaaaa");
/* ThreadGroup threadGroup = new ThreadGroup("MyGroup") {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("捕获到异常。异常栈信息为:"+e.getMessage());
e.printStackTrace();
}
};*/
//Thread t = new Thread(threadGroup, () -> {
Thread.setDefaultUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
Thread t = new Thread(() -> {
System.out.println(1 / 0);
System.out.println("ccccccccccccc");
}
);
// t.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
t.start();
System.out.println("bbbbbbbbbbbbbbbbb");
}
(1)在提交的任务中将异常捕获并处理,不抛给线程池。
(2)异常抛给线程池,但是我们要及时处理抛出的异常。
简单代码例子
@Test
void ThreadTest() {
System.out.println("aaaaaaaaaaa");
/* ThreadGroup threadGroup = new ThreadGroup("MyGroup") {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("捕获到异常。异常栈信息为:"+e.getMessage());
e.printStackTrace();
}
};*/
//Thread t = new Thread(threadGroup, () -> {
/*Thread.setDefaultUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
Thread t = new Thread(() -> {
System.out.println(1 / 0);
System.out.println("ccccccccccccc");
}
);
// t.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
t.start();
System.out.println("bbbbbbbbbbbbbbbbb");*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 21, 3000, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
log.info("开始提交任务");
executor.execute(DwckzbServiceTest::doSomeThing);
log.info("提交任务完成");
}
private static void doSomeThing() {
int value = 5 / 0;
}
结果
aaaaaaaaaaa
2022-04-13 17:06:05.927 INFO 56408 — [ main]: 开始提交任务
2022-04-13 17:06:05.928 INFO 56408 — [ main] 提交任务完成
Exception in thread “pool-6-thread-1” java.lang.ArithmeticException: / by zero
java.lang.IllegalStateException: Shutdown in progress
at java.lang.ApplicationShutdownHooks.remove(ApplicationShutdownHooks.java:82)
at java.lang.Runtime.removeShutdownHook(Runtime.java:239)
at org.apache.rocketmq.client.trace.AsyncTraceDispatcher.removeShutdownHook(AsyncTraceDispatcher.java:210)
线程池提交任务有两种方式,一种是 execute 方式,另一种是 submit 方式。
第一种思路很简单,就是我们提交任务的时候,将所有可能的异常都Catch住,并且自己处理。
@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {
@Test
void saveBatchTest() {
ThreadPoolExecutor executor = poolExecutor(1, 1);
log.info("开始提交任务");
executor.execute(LiuTest::doSomeThing);
log.info("提交任务完成");
}
public static ThreadPoolExecutor poolExecutor(int core, int max) {
return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
}
private static ReturnModel doSomeThing() {
int result ;
try {
int value = 10 / 0;
result = 2;
} catch (Exception e) {
log.error("doSomeThing execute Exception: ", e);
return ReturnModel.newFailureInstance();
}
return ReturnModel.newSuccessInstance(result);
}
}
说白了就是把业务逻辑都trycatch起来。 但是这种思路的缺点就是:
所有的不同任务类型都要trycatch,增加了代码量。
不存在checkedexception的地方也需要都trycatch起来,代码丑陋。
@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {
@Test
void liuTest() {
ThreadPoolExecutor executor = poolExecutor(1, 1);
log.info("开始提交任务");
executor.execute(LiuTest::doSomeThing);
log.info("提交任务完成");
}
/*public static ThreadPoolExecutor poolExecutor(int core, int max) {
return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
}*/
public static ThreadPoolExecutor poolExecutor(int core, int max) {
return new CustomThreadPoolExecutor(1,2,60,TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
}
private static ReturnModel doSomeThing() {
int result ;
/* try {
int value = 10 / 0;
result = 2;
} catch (Exception e) {
log.error("doSomeThing execute Exception: ", e);
return ReturnModel.newFailureInstance();
}*/
int value = 10 / 0;
result = 2;
return ReturnModel.newSuccessInstance(result);
}
static class CustomThreadPoolExecutor extends ThreadPoolExecutor {
public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
@Override
protected void afterExecute(Runnable r, Throwable e) {
super.afterExecute(r, e);
log.error(e.getMessage());
log.error("CustomThreadPoolExecutor execute Exception: "+
ThreadUtils.stackTrace(e.getStackTrace()));
}
}
static class ThreadUtils {
public static String stackTrace(StackTraceElement[] stackTrace) {
if (stackTrace != null && stackTrace.length > 0) {
StringBuilder logs = new StringBuilder(512);
for (StackTraceElement e : stackTrace) {
logs.append(java.text.MessageFormat.format("{0}: {1}(): {2}" , e.getClassName(), e.getMethodName(),
e.getLineNumber())).append("\n");
}
return logs.toString();
}
return "";
}
}
}
运行结果
2022-04-14 09:55:49.651 INFO 7916 --- [ main] INFO LiuTest : 开始提交任务
2022-04-14 09:55:49.652 INFO 7916 --- [ main] INFO .LiuTest : 提交任务完成
2022-04-14 09:55:49.652 ERROR 7916 --- [pool-6-thread-1] ERROR LiuTest : / by zero
2022-04-14 09:55:49.654 ERROR 7916 --- [pool-6-thread-1] ERROR LiuTest : CustomThreadPoolExecutor execute Exception: LiuTest: doSomeThing(): 44
java.util.concurrent.ThreadPoolExecutor: runWorker(): 1,142
java.util.concurrent.ThreadPoolExecutor$Worker: run(): 617
java.lang.Thread: run(): 745
@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {
@Test
void liuTest() {
ThreadPoolExecutor executor = poolExecutor(1, 1);
log.info("开始提交任务");
executor.execute(LiuTest::doSomeThing);
log.info("提交任务完成");
}
/*public static ThreadPoolExecutor poolExecutor(int core, int max) {
return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
}*/
/*public static ThreadPoolExecutor poolExecutor(int core, int max) {
return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), r -> new Thread(new CustomThreadGroup("CustomThreadGroup"), r),
new ThreadPoolExecutor.AbortPolicy());
}*/
public static ThreadPoolExecutor poolExecutor(int core, int max) {
return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), r -> {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler((t, e) ->
log.error("CustomThreadPoolExecutor execute Exception: {}",
ThreadUtils.stackTrace(e.getStackTrace())));
return thread;
}, new ThreadPoolExecutor.AbortPolicy());
}
static class CustomThreadGroup extends ThreadGroup {
public CustomThreadGroup(String name) {
super(name);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
super.uncaughtException(t, e);
log.error(e.getMessage());
log.error("CustomThreadPoolExecutor execute Exception: {}",
ThreadUtils.stackTrace(e.getStackTrace()));
}
}
private static ReturnModel doSomeThing() {
int result ;
/* try {
int value = 10 / 0;
result = 2;
} catch (Exception e) {
log.error("doSomeThing execute Exception: ", e);
return ReturnModel.newFailureInstance();
}*/
int value = 10 / 0;
result = 2;
return ReturnModel.newSuccessInstance(result);
}
/* static class CustomThreadPoolExecutor extends ThreadPoolExecutor {
public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
@Override
protected void afterExecute(Runnable r, Throwable e) {
super.afterExecute(r, e);
log.error(e.getMessage());
log.error("CustomThreadPoolExecutor execute Exception: "+
ThreadUtils.stackTrace(e.getStackTrace()));
}
}*/
static class ThreadUtils {
public static String stackTrace(StackTraceElement[] stackTrace) {
if (stackTrace != null && stackTrace.length > 0) {
StringBuilder logs = new StringBuilder(512);
for (StackTraceElement e : stackTrace) {
logs.append(java.text.MessageFormat.format("{0}: {1}(): {2}" , e.getClassName(), e.getMethodName(),
e.getLineNumber())).append("\n");
}
return logs.toString();
}
return "";
}
}
}
除了可以自定义线程池,也可以自定义 ThreadGroup,这样就不会调用默认的 ThreadGroup,走 System.err 的逻辑了。
覆盖其uncaughtException方法。
@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {
@Test
void liuTest() {
ThreadPoolExecutor executor = poolExecutor(1, 1);
log.info("开始提交任务");
executor.execute(LiuTest::doSomeThing);
log.info("提交任务完成");
}
/*public static ThreadPoolExecutor poolExecutor(int core, int max) {
return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
}*/
public static ThreadPoolExecutor poolExecutor(int core, int max) {
return new ThreadPoolExecutor(core, max, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), r -> new Thread(new CustomThreadGroup("CustomThreadGroup"), r),
new ThreadPoolExecutor.AbortPolicy());
}
static class CustomThreadGroup extends ThreadGroup {
public CustomThreadGroup(String name) {
super(name);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
super.uncaughtException(t, e);
log.error(e.getMessage());
log.error("CustomThreadPoolExecutor execute Exception: {}",
ThreadUtils.stackTrace(e.getStackTrace()));
}
}
private static ReturnModel doSomeThing() {
int result ;
/* try {
int value = 10 / 0;
result = 2;
} catch (Exception e) {
log.error("doSomeThing execute Exception: ", e);
return ReturnModel.newFailureInstance();
}*/
int value = 10 / 0;
result = 2;
return ReturnModel.newSuccessInstance(result);
}
static class CustomThreadPoolExecutor extends ThreadPoolExecutor {
public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
@Override
protected void afterExecute(Runnable r, Throwable e) {
super.afterExecute(r, e);
log.error(e.getMessage());
log.error("CustomThreadPoolExecutor execute Exception: "+
ThreadUtils.stackTrace(e.getStackTrace()));
}
}
static class ThreadUtils {
public static String stackTrace(StackTraceElement[] stackTrace) {
if (stackTrace != null && stackTrace.length > 0) {
StringBuilder logs = new StringBuilder(512);
for (StackTraceElement e : stackTrace) {
logs.append(java.text.MessageFormat.format("{0}: {1}(): {2}" , e.getClassName(), e.getMethodName(),
e.getLineNumber())).append("\n");
}
return logs.toString();
}
return "";
}
}
}
运行结果同上
尤其注意:上面三种方式针对的都是通过execute(xx)的方式提交任务,如果你提交任务用的是submit()方法,那么上面的三种方式都将不起作用,而应该使用下面的方式
如果提交任务的时候使用的方法是submit,那么该方法将返回一个Future对象,所有的异常以及处理结果都可以通过future对象获取。 采用Future模式,将返回结果以及异常放到Future中,在Future中处理
@SpringBootTest(classes = MatnrApplication.class)
@Slf4j
public class LiuTest {
@Test
void liuTest() {
List> results = new ArrayList>();
ThreadPoolExecutor es = poolExecutor(1, 1);
log.info("开始提交任务");
for(int i=0; i<100;i++)
results.add(es.submit(new Task()));
for(Future res : results){
try{
int value = 10 / 0;
System.out.println(res.get());
}catch (Exception e){
log.error("CustomThreadPoolExecutor execute Exception: {}",
e.getMessage());
}
}
log.info("提交任务完成");
}
static class Task implements Callable {
@Override
public String call() throws Exception {
String tid = String.valueOf(Thread.currentThread().getId());
System.out.printf("Thread#%s : in call\n", tid);
return tid;
}
}
ThreadPoolExecutor
sumbit() 方式底层使用 FutureTask 执行任务,如果业务抛出异常,只有在调用 Future#get() 时才会被抛出。
execute() 方法底层使用 Thread 执行任务,如果业务抛出异常,默认采用 Sysstem.err 进行输出,只会打印在控制台和 tomcat 的 catalina.out 文件,不会输出到日志中。
sumbit() 方法处理异常,既可以在业务中进行手动 catch,也可以在调用 Future#get() 时手动 catch。
execute() 方法处理异常:
业务中手动 catch,每个业务地方都要写,最稳妥。
自定义 ThreadPoolExecutor 或者自定义 ThreadGroup,控制台会打印两遍日志。
设置 UncaughtExceptionHandler,控制台只打印一遍日志。