捕获线程中的异常

简介:

由于线程的本质特性,使得你不能捕获从线程中逃逸的异常。一旦异常逃出任务的run()方法它就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常。在Java SE5之前,你可以使用线程组来捕捉这种异常,但是有了Java SE5,就可以用Executor来解决这个问题了。

下面的任务总是会抛出一个异常,该异常会传播到其run()方法的外部,并且main()展示了当你运行它时所发生的事情:

?
1
2
3
4
5
6
7
8
9
10
11
12
import  java.util.concurrent.ExecutorService;
import  java.util.concurrent.Executors;
 
public  class  ExceptionThread  implements  Runnable {
     public  void  run() {
         throw  new  RuntimeException();
     }
     public  static  void  main(String[] args) {
         ExecutorService service = Executors.newCachedThreadPool();
         service.execute( new  ExceptionThread());
     }
}

输出如下:

?
1
2
3
4
5
Exception in thread  "pool-1-thread-1"  java.lang.RuntimeException
     at com.abc.thread.ExceptionThread.run(ExceptionThread.java: 6 )
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java: 1145 )
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java: 615 )
     at java.lang.Thread.run(Thread.java: 745 )

将main的主体放在try-catch语句块中也是没有作用的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import  java.util.concurrent.ExecutorService;
import  java.util.concurrent.Executors;
 
public  class  ExceptionThread  implements  Runnable {
     public  void  run() {
         throw  new  RuntimeException();
     }
     public  static  void  main(String[] args) {
         try  {
             ExecutorService service = Executors.newCachedThreadPool();
             service.execute( new  ExceptionThread());
         catch  (RuntimeException e) {
             System.out.println( "Catched Runtime Exception." );
         }
     }
}

这将产生于前面示例相同的结果:未捕获的异常。

为了解决这个问题,我们要修改Executor产生线程的方式。Thread.UncaughtExceptionHandler是Java SE5中的新接口,它允许你在每个Thread对象上都附着一个异常处理器。Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。为了使用它,我们创建了一个新类型的ThreadFactory,它将在每个新创建的Thread对象上附着一个Thread.UncaughtExceptionHandler。

?
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
33
34
35
import  java.util.concurrent.ExecutorService;
import  java.util.concurrent.Executors;
import  java.util.concurrent.ThreadFactory;
 
public  class  ExceptionThread2  implements  Runnable {
     public  void  run() {
         throw  new  RuntimeException( "NullPointer" );
     }
     
     public  static  void  main(String[] args) {
         ThreadFactory tFactory =  new  MyThreadFactory();
         ExecutorService service = Executors.newCachedThreadPool(tFactory);
         Runnable task =  new  ExceptionThread2();
         service.execute(task);
     }
}
 
class  MyUncaughtExceptionHandler  implements  Thread.UncaughtExceptionHandler {
     // 处理从线程里抛出来的异常。
     public  void  uncaughtException(Thread t, Throwable e) {
         System.out.println( "Catched Throwable: " 
                 e.getClass().getSimpleName() +  ", "  + e.getMessage());
     }
}
 
class  MyThreadFactory  implements  ThreadFactory {
     // 重新组织创建线程的方式
     public  Thread newThread(Runnable r) {
         Thread t =  new  Thread(r);
         // 为每一个线程都绑定一个异常处理器。
         t.setUncaughtExceptionHandler( new  MyUncaughtExceptionHandler());
         System.out.println( "Thread["  + t.getName() +  "] created." );
         return  t;
     }
}

执行的结果如下:

可以看到,线程池中有2个线程,当一个线程发生异常时,该异常被捕捉了。

上面的示例使得你可以按照具体情况(在newThread()方法中使用if, case等语句)为每个线程逐个的设置处理器。如果你知道将要在代码中处处使用相同的异常处理器,那么更简单的方式是在Thread类中设置一个静态域,并将这个处理器设置为默认的处理器即可:

?
1
2
3
4
5
6
7
8
9
10
11
import  java.util.concurrent.ExecutorService;
import  java.util.concurrent.Executors;
 
public  class  SettingDefaultHandler {
     public  static  void  main(String[] args) {
         // 为线程设置默认的异常处理器。
         Thread.setDefaultUncaughtExceptionHandler( new  MyUncaughtExceptionHandler());
         ExecutorService exec = Executors.newCachedThreadPool();
         exec.execute( new  ExceptionThread2());
     }
}

这个处理器只有在不存在线程专有的未捕获异常处理器的情况下才会被调用。系统会检查线程专有版本,如果没有发现,则检查线程组是否有专有的uncaughtException()方法,如果也没有,才会调用defaultUncaughtExceptionHandler。

目录
相关文章
|
2月前
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
82 0
|
1天前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
2月前
|
监控 Java
在实际应用中选择线程异常捕获方法的考量
【10月更文挑战第15天】选择最适合的线程异常捕获方法需要综合考虑多种因素。没有一种方法是绝对最优的,需要根据具体情况进行权衡和选择。在实际应用中,还需要不断地实践和总结经验,以提高异常处理的效果和程序的稳定性。
30 3
|
2月前
|
监控 Java
捕获线程执行异常的多种方法
【10月更文挑战第15天】捕获线程执行异常的方法多种多样,每种方法都有其特点和适用场景。在实际开发中,需要根据具体情况选择合适的方法或结合多种方法来实现全面有效的线程异常捕获。这有助于提高程序的健壮性和稳定性,减少因线程异常带来的潜在风险。
31 1
|
2月前
|
监控 API
Hook 线程与捕获线程执行异常
【10月更文挑战第11天】Hook 线程和捕获线程执行异常是多线程编程中不可或缺的技术。通过深入理解和掌握这些方法,我们可以提高程序的稳定性和可靠性,更好地应对各种异常情况。同时,在实际应用中要注意平衡性能和准确性,制定合理的异常处理策略,以确保程序的正常运行。
38 1
|
3月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
86 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
3月前
|
监控 Java
线程池中线程异常后:销毁还是复用?技术深度剖析
在并发编程中,线程池作为一种高效利用系统资源的工具,被广泛用于处理大量并发任务。然而,当线程池中的线程在执行任务时遇到异常,如何妥善处理这些异常线程成为了一个值得深入探讨的话题。本文将围绕“线程池中线程异常后:销毁还是复用?”这一主题,分享一些实践经验和理论思考。
166 3
|
4月前
|
Java
线程池中线程抛了异常,该如何处理?
【8月更文挑战第27天】在Java多线程编程中,线程池(ThreadPool)是一种常用的并发处理工具,它能够有效地管理线程的生命周期,提高资源利用率,并简化并发编程的复杂性。然而,当线程池中的线程在执行任务时抛出异常,如果不妥善处理,这些异常可能会导致程序出现未预料的行为,甚至崩溃。因此,了解并掌握线程池异常处理机制至关重要。
537 0
|
3天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
14 1