3、Java多线程-处理子线程异常

处理子线程异常(重要)、
参考:https://www.cnblogs.com/jpfss/p/10272066.html
1、Java子线程中的异常处理
  父线程中启动子线程,直接在父线程启动子线程的地方try...catch,是捕获不到子线程的异常的
  原因:Runnable接口的run方法的完整签名,因为没有标识throws语句,所以方法是不会抛出checked异常的。至于RuntimeException这样的 unchecked异常,由于新线程由JVM进行调度执行,如果发生了异常,也不会通知到父线程。
2、处理子线程的异常
  子线程中处理:
    a.子线程中try...catch
    b.为子线程设置“未捕获异常处理器”UncaughtExceptionHandler
    既然a方法已经可以捕获异常,为什么还要有b存在,我的理解是a需要指定可能发生异常的代码,而b不需要指定,只要发生异常,对应的异常处理器自动处理。
  父线程中处理:
    c.通过Future的get方法捕获异常(推荐)

3、示例代码

  1 import java.util.ArrayList;
  2 import java.util.List;
  3 import java.util.concurrent.Callable;
  4 import java.util.concurrent.ExecutionException;
  5 import java.util.concurrent.ExecutorService;
  6 import java.util.concurrent.Executors;
  7 import java.util.concurrent.Future;
  8 
  9 public class TestExceptionThred {
 10     
 11     /**
 12      * @param args
 13      */
 14     public static void main(String[] args) {
 15         ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
 16         List<Future<String>> fetrueResult = new ArrayList<>();
 17         System.out.println("start");
 18         
 19         try {
 20             System.out.println("ssssssss");
 21 //            newFixedThreadPool.execute(new ChildThread01());
 22 //            newFixedThreadPool.execute(new ChildThread01());
 23 //            newFixedThreadPool.execute(new ChildThread01());
 24 
 25 //            newFixedThreadPool.execute(new ChildThread0101());
 26 //            newFixedThreadPool.execute(new ChildThread0101());
 27 
 28 //            newFixedThreadPool.execute(new ChildThread02());
 29 //            newFixedThreadPool.execute(new ChildThread02());
 30 
 31 //            newFixedThreadPool.execute(new ChildThread0202());
 32 //            newFixedThreadPool.execute(new ChildThread0202());
 33             
 34             Future<String> result01 = newFixedThreadPool.submit(new ChildThread03());
 35             fetrueResult.add(result01);
 36             Future<String> result02 = newFixedThreadPool.submit(new ChildThread03());
 37             fetrueResult.add(result02);
 38             for (Future<String> result : fetrueResult) {
 39                 result.get();
 40             }
 41             System.out.println("eeeeeeeee");
 42         } catch (InterruptedException | ExecutionException e) {
 43             System.out.println("InterruptedException or ExecutionException has been handled");
 44         } catch (Exception e) {
 45             System.out.println("exception has been handled");
 46         } finally {
 47             System.out.println("finally");
 48             if (null != newFixedThreadPool) {
 49                 newFixedThreadPool.shutdown();
 50             }
 51         }
 52         System.out.println("end");
 53     }
 54     
 55 }
 56 
 57 /**
 58  * 子线程中发生异常,未处理直接抛出,这种情况下,子线程直接退出,且不会记录任何日志
 59  */
 60 class ChildThread01 implements Runnable {
 61     
 62     /*
 63      * @see java.lang.Runnable#run()
 64      */
 65     @Override
 66     public void run() {
 67         System.out.println("ChildThread before exception");
 68         exceptionMethod();
 69         System.out.println("ChildThread before exception");
 70     }
 71     
 72     private void exceptionMethod() {
 73         throw new RuntimeException("ChildThread01 exception");
 74     }
 75 }
 76 
 77 /**
 78  * 解决方案1:在子线程中try...catch捕获异常
 79  * 子线程中发生异常,并在子线程中处理
 80  */
 81 /**
 82  * 为线程设置异常处理器。具体做法可以是以下几种:
 83  * (1)Thread.setUncaughtExceptionHandler设置当前线程的异常处理器;
 84  * (2)Thread.setDefaultUncaughtExceptionHandler为整个程序设置默认的异常处理器;
 85  * 如果当前线程有异常处理器(默认没有),则优先使用该UncaughtExceptionHandler类;
 86  * 否则,如果当前线程所属的线程组有异常处理器,则使用线程组的UncaughtExceptionHandler;
 87  * 否则,使用全局默认的DefaultUncaughtExceptionHandler;如果都没有的话,子线程就会退出。
 88  * 注意:子线程中发生了异常,如果没有任何类来接手处理的话,是会直接退出的,而不会记录任何日志。
 89  * 所以,如果什么都不做的话,是会出现子线程任务既没执行成功,也没有任何日志提示的“诡异”现象的。
 90  */
 91 class ChildThread0101 implements Runnable {
 92     
 93     /*
 94      * @see java.lang.Runnable#run()
 95      */
 96     @Override
 97     public void run() {
 98         //可能发生异常的地方,用try...catch处理
 99         try {
100             System.out.println("ChildThread0101 before exception");
101             exceptionMethod();
102             System.out.println("ChildThread0101 before exception");
103         } catch (Exception e) {
104             System.out.println("exception has been handled in ChildThread0101");
105         }
106     }
107     
108     private void exceptionMethod() {
109         throw new RuntimeException("ChildThread0101 exception");
110     }
111 }
112 
113 /**
114  * 解决方案2:为子线程设置“未捕获异常处理器”UncaughtExceptionHandler
115  * 子线程中发生异常,并在子线程中处理
116  */
117 class ChildThread02 implements Runnable {
118     private static ChildThreadExceptionHandler exceptionHandler;
119     
120     static {
121         exceptionHandler = new ChildThreadExceptionHandler();
122     }
123     
124     @Override
125     public void run() {
126         //下面代码可能会发生异常,但是不需要用try...catch显示的包裹代码处理,
127         //定义的异常处理器会自动捕获异常,并在子线程中处理
128         
129         //设置当前线程的异常处理器
130         Thread.currentThread().setUncaughtExceptionHandler(exceptionHandler);
131         System.out.println("ChildThread02 do something 1");
132         exceptionMethod();
133         System.out.println("ChildThread02 do something 2");
134     }
135     
136     private void exceptionMethod() {
137         throw new RuntimeException("ChildThread02 exception");
138     }
139     
140     //为线程设置“未捕获异常处理器”UncaughtExceptionHandler
141     public static class ChildThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
142         public void uncaughtException(Thread t, Throwable e) {
143             System.out.println(String.format("handle exception in ChildThread02. %s", e));
144         }
145     }
146 }
147 
148 /**
149  * 解决方案2
150  * 子线程中发生异常,并在子线程中处理
151  */
152 class ChildThread0202 implements Runnable {
153     private static ChildThreadExceptionHandler exceptionHandler;
154     
155     static {
156         exceptionHandler = new ChildThreadExceptionHandler();
157         //设置所有线程的默认异常处理器
158         Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
159     }
160     
161     public void run() {
162         System.out.println("ChildThread0202 do something 1");
163         exceptionMethod();
164         System.out.println("ChildThread0202 do something 2");
165     }
166     
167     private void exceptionMethod() {
168         throw new RuntimeException("ChildThread0202 exception");
169     }
170     
171     public static class ChildThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
172         public void uncaughtException(Thread t, Throwable e) {
173             System.out.println(String.format("handle exception in ChildThread0202. %s", e));
174         }
175     }
176 }
177 
178 /**
179  * 解决方案3:通过Future的get方法捕获异常(推荐)
180  */
181 /**
182  * 使用线程池提交一个能获取到返回信息的方法,也就是ExecutorService.submit(Callable)
183  * 在submit之后可以获得一个线程执行结果的Future对象,而如果子线程中发生了异常,
184  * 通过future.get()获取返回值时,可以捕获到ExecutionException异常,从而知道子线程中发生了异常
185  * 
186  * 注意,如果不调用future.get(),则不会捕获到异常;如果子线程发生异常,直接退出,无任何记录
187  * 如果启动了多个子线程,捕获到任何一个子线程的异常,父线程执行finally语句或执行后续代码
188  */
189 class ChildThread03 implements Callable<String> {
190     public String call() throws Exception {
191         System.out.println("ChildThread03 do something 1");
192         exceptionMethod();
193         System.out.println("ChildThread03 do something 2");
194         return "ChildThread03 test result";
195     }
196     
197     private void exceptionMethod() {
198         throw new RuntimeException("ChildThread03 exception");
199     }
200 }