你处理过多线程中的异常吗?如何捕获多线程中发生的异常?捕获子线程的异常与捕获当前线程的异常一样简单吗?
除了try catch。Java中还可以通过异常处理器
UncaughtExceptionHandler
来处理那些未捕获的异常。
# 在当前线程捕获当前线程发生的异常:
/***@authorfutao*@date/6/17*/@Slf4jpublicclassExceptionInCurThread{publicstaticvoidmain(String[]args){try{thrownewRuntimeException("在主线程抛出异常,在主线程捕获");}catch(RuntimeExceptione){log.error("捕获到异常",e);}}}
结果:
结论:在当前线程通过try catch
可以捕获当前线程抛出的异常。
# 可以在当前通过try catch
的方式捕获其他线程抛出的异常吗?'
/***@author喜欢天文的pony站长*Createdon/6/16.*/publicclassExceptionInChildThreadimplementsRunnable{@Overridepublicvoidrun(){thrownewRuntimeException("子线程发生了异常...");}/***模拟子线程发生异常**@throwsInterruptedException*/privatestaticvoidexceptionThread()throwsInterruptedException{newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}/***在主线程尝试通过trycatch捕获异常*/privatestaticvoidcatchInMain(){try{exceptionThread();}catch(Exceptione){//无法捕获发生在其他线程中的异常log.error("捕获到了异常?",e);}}publicstaticvoidmain(String[]args)throwsInterruptedException{ExceptionInChildThread.catchInMain();}}
(错误的)预期:
在运行第一个线程的时候发生了异常,被catch捕获,打印捕获到了异常?
和异常堆栈且后面的线程将不会运行。
实际运行结果:
并不符合预期。
没有被try catch捕获。
后续的线程没有因为第一个线程发生异常而跳过。
结论:
无法在一个线程中通过try catch捕获另外一个线程的异常。
# 解决方案
在每个线程内部run()方法内通过try catch捕获当前线程发生的异常。
缺点:每个线程都需要编写重复的try catch 代码
使用线程异常处理器UncaughtExceptionHandler
给所有线程设置统一的异常处理器
给每个线程设置特定的异常处理器
给线程组设置异常处理器
给线程池设置异常处理器
因为线程池也是通过new Thread()
的方式创建的线程,所以思想与上面两种方法一致。
注意:execute()
与submit()
方式对异常处理的不同。
# 在线程内部run()
通过try catch捕获异常
/***@author喜欢天文的pony站长*Createdon/6/16.*/@Slf4jpublicclassExceptionInChildThreadimplementsRunnable{@Overridepublicvoidrun(){try{//dosomethingelse...thrownewRuntimeException("子线程发生了异常...");}catch(Exceptione){log.error("在线程内部捕获异常",e);}}/***模拟子线程发生异常**@throwsInterruptedException*/privatestaticvoidexceptionThread()throwsInterruptedException{newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}/***在主线程尝试通过trycatch捕获异常*/privatestaticvoidcatchInMain(){try{exceptionThread();}catch(Exceptione){//无法捕获发生在其他线程中的异常log.error("捕获到了异常?",e);}}publicstaticvoidmain(String[]args)throwsInterruptedException{ExceptionInChildThread.catchInMain();}}
结果:
成功在子线程内部run()
方法捕获到了异常
# 使用线程异常处理器UncaughtExceptionHandler
当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器
自定义线程异常处理器
/***自定义线程未捕获异常处理器**@authorfutao*@date/6/17*/publicclassCustomThreadUncaughtExceptionHandlerimplementsThread.UncaughtExceptionHandler{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(CustomThreadUncaughtExceptionHandler.class);@OverridepublicvoiduncaughtException(Threadt,Throwablee){LOGGER.error("捕获到线程发生的异常,线程信息:[{}]",JSON.toJSONString(t),e);}}
使用:
1. 全局:
Thread.setDefaultUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
通过调用Thread的静态方法setDefaultUncaughtExceptionHandler()
,设置Thread的静态属性defaultUncaughtExceptionHandler
.为我们自定义的异常处理器。
源码:
测试:
/***@author喜欢天文的pony站长*Createdon/6/16.*/@Slf4jpublicclassExceptionInChildThreadimplementsRunnable{@Overridepublicvoidrun(){thrownewRuntimeException("子线程发生了异常...");}/***模拟子线程发生异常**@throwsInterruptedException*/privatestaticvoidexceptionThread()throwsInterruptedException{newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}publicstaticvoidmain(String[]args)throwsInterruptedException{//设置全局的线程异常处理器Thread.setDefaultUncaughtExceptionHandler(newCustomThreadUncaughtExceptionHandler());exceptionThread();}}
结果: 成功捕获
2. 为指定线程设置特定的异常处理器
细心的同学已经发现了,在上面Thread类的截图中,还有一个实例属性private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
。通过给这个属性赋值,可以实现为每个线程对象设置不同的异常处理器。
测试使用
/***@author喜欢天文的pony站长*Createdon/6/16.*/@Slf4jpublicclassExceptionInChildThreadimplementsRunnable{@Overridepublicvoidrun(){thrownewRuntimeException("子线程发生了异常...");}/***模拟子线程发生异常**@throwsInterruptedException*/privatestaticvoidexceptionThread()throwsInterruptedException{Threadthread1=newThread(newExceptionInChildThread());//为指定线程设置特定的异常处理器thread1.setUncaughtExceptionHandler(newCustomThreadUncaughtExceptionHandler());thread1.start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);newThread(newExceptionInChildThread()).start();TimeUnit.MILLISECONDS.sleep(200L);}publicstaticvoidmain(String[]args)throwsInterruptedException{exceptionThread();}}
结果: 成功捕获线程1的异常信息
3. 线程组
/***@authorfutao*@date/6/20*/@Slf4jpublicclassExceptionInThreadGroupimplementsRunnable{@Overridepublicvoidrun(){thrownewRuntimeException("线程任务发生了异常");}publicstaticvoidmain(String[]args)throwsInterruptedException{ThreadGroupthreadGroup=newThreadGroup("只知道抛出异常的线程组..."){@OverridepublicvoiduncaughtException(Threadt,Throwablee){super.uncaughtException(t,e);log.error("线程组内捕获到线程[{},{}]异常",t.getId(),t.getName(),e);}};ExceptionInThreadGroupexceptionInThreadGroup=newExceptionInThreadGroup();newThread(threadGroup,exceptionInThreadGroup,"线程1").start();TimeUnit.MILLISECONDS.sleep(300L);//优先获取绑定在thread对象上的异常处理器Threadthread=newThread(threadGroup,exceptionInThreadGroup,"线程2");thread.setUncaughtExceptionHandler(newCustomThreadUncaughtExceptionHandler());thread.start();TimeUnit.MILLISECONDS.sleep(300L);newThread(threadGroup,exceptionInThreadGroup,"线程3").start();}}
结果:
4. 线程池
/***@authorfutao*@date/6/17*/publicclassCatchThreadPoolException{publicstaticvoidmain(String[]args){ThreadPoolExecutorthreadPoolExecutor=newThreadPoolExecutor(2,4,1L,TimeUnit.MINUTES,newLinkedBlockingDeque<>(1024),newThreadFactory(){@OverridepublicThreadnewThread(Runnabler){Threadthread=newThread(r);//设置线程异常处理器thread.setUncaughtExceptionHandler(newCustomThreadUncaughtExceptionHandler());returnthread;}});threadPoolExecutor.execute(newRunnable(){@Overridepublicvoidrun(){thrownewRuntimeException("execute()发生异常");}});threadPoolExecutor.submit(newRunnable(){@Overridepublicvoidrun(){thrownewRuntimeException("submit.run()发生异常");}});threadPoolExecutor.submit(newCallable<String>(){@OverridepublicStringcall()throwsException{thrownewRuntimeException("submit.call()发生异常");}});threadPoolExecutor.shutdown();}}
结果: 并不符合预期,预期应该捕获三个异常
只捕获到了通过execute()提交的任务的异常
没有捕获到通过submit()提交的任务的异常
通过afterExecute()
捕获submit()
任务的异常
通过submit()
方法的源码可以发现,submit()
是将runnable()
封装成了RunnableFuture<Void>
,并最终调用execute(ftask);
执行。
/***@authorfutao*@date/6/17*/@Slf4jpublicclassCatchThreadPoolException{publicstaticvoidmain(String[]args)throwsInterruptedException,ExecutionException{ThreadPoolExecutorthreadPoolExecutor=newThreadPoolExecutor(2,4,1L,TimeUnit.MINUTES,newLinkedBlockingDeque<>(1024),newThreadFactory(){@OverridepublicThreadnewThread(Runnabler){Threadthread=newThread(r);//设置线程异常处理器thread.setUncaughtExceptionHandler(newCustomThreadUncaughtExceptionHandler());returnthread;}}){/***捕获{@codeFutureTask<?>}抛出的异常**@paramr*@paramt*/@OverrideprotectedvoidafterExecute(Runnabler,Throwablet){super.afterExecute(r,t);if(rinstanceofFutureTask<?>){try{//get()的时候会将异常内的异常抛出((FutureTask<?>)r).get();}catch(InterruptedExceptione){e.printStackTrace();Thread.currentThread().interrupt();}catch(ExecutionExceptione){log.error("捕获到线程的异常返回值",e);}}//Throwablet永远为null,拿不到异常信息//log.error("afterExecute中捕获到异常,", t);}};threadPoolExecutor.execute(newRunnable(){@Overridepublicvoidrun(){thrownewRuntimeException("execute()发生异常");}});TimeUnit.MILLISECONDS.sleep(200L);threadPoolExecutor.submit(newRunnable(){@Overridepublicvoidrun(){thrownewRuntimeException("submit.run()发生异常");}});TimeUnit.MILLISECONDS.sleep(200L);threadPoolExecutor.submit(newCallable<String>(){@OverridepublicStringcall()throwsException{thrownewRuntimeException("submit.call()发生异常");}}).get();//get()的时候会将异常抛出threadPoolExecutor.shutdown();}}
欢迎在评论区留下你看文章时的思考,及时说出,有助于加深记忆和理解,还能和像你一样也喜欢这个话题的读者相遇~
# 本文源代码
/FutaoSmile/learn-thread/tree/master/src/main/java/com/futao/learn/threads/捕获线程异常