線程任務異常終止問題


  本文為博主原創文章,未經博主允許不得轉載。


  我們開發工程中經常使用到線程,在線程使用上,我們可能會有這樣的場景:

  1. 伴隨這一個業務產生一個比較耗時的任務,而這個業務返回並不需要等待該任務。那我們往往會啟動一個線程去完成這個異步任務。
  2. 我們需要一個定時任務比如:定時清除數據,我們會起一個定時執行線程去做該任務。

  上述問題比較簡單,new一個線程然后去做這件事。但是我們常常忽略一個問題,線程異常了怎么辦?

  比如耗時任務我們只完成了一半,我們就異常結束了(這里不考慮事務一致性,我們只考慮一定要將任務完成)。又比如在清數據的時候,數據庫發生斷連。這時候我們會發現線程死掉了,任務終止了,我們需要重啟整個項目把該定時任務起起來。

  解決這些問題的關鍵就是,如何捕獲線程執行過程中產生的異常?我們查看JDK API我們會發現在Thread中有setUncaughtExceptionHandler方法,讓我們可以在線程發生異常時,調用該方法。

場景一解決思路:

 

 1 public class Plan1 {
 2     
 3     private SimpleTask task = new SimpleTask();
 4     
 5     public static void main(String[] args) {
 6         Plan1 plan = new Plan1();
 7         plan.start();
 8     }
 9     public void start(){
10         Thread thread = new Thread(task);
11         //thread.setDaemon(true); //注釋調 否則看不到輸出
12         thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){
13             @Override
14             public void uncaughtException(Thread t, Throwable e) {
15                 System.out.println(e.getMessage());
16                 start();
17             }
18         });
19         thread.start();
20     }
21     
22     class SimpleTask implements Runnable{
23         private int task = 10;
24         @Override
25         public void run() {
26             String threadName = Thread.currentThread().getName();
27             System.out.println(threadName+"--"+"啟動");
28             while(task>0){
29                 try {
30                     Thread.sleep(100);
31                 } catch (InterruptedException e) {
32                     e.printStackTrace();
33                 }
34                 if(System.currentTimeMillis()%3==0){
35                     throw new RuntimeException("模擬異常");
36                 }
37                 System.out.println(threadName+"--"+"執行task"+task);
38                 task--;
39             }
40             System.out.println(threadName+"--"+"正常終止");
41         }
42     }
43 }

 

結果輸出:

 

 1 Thread-0--啟動
 2 Thread-0--執行task10
 3 Thread-0--執行task9
 4 Thread-0--執行task8
 5 Thread-0--執行task7
 6 模擬異常
 7 Thread-1--啟動
 8 Thread-1--執行task6
 9 Thread-1--執行task5
10 模擬異常
11 Thread-2--啟動
12 Thread-2--執行task4
13 Thread-2--執行task3
14 模擬異常
15 Thread-3--啟動
16 Thread-3--執行task2
17 模擬異常
18 Thread-4--啟動
19 Thread-4--執行task1
20 Thread-4--正常終止

還是場景一我們來看一下線程池的方式,思路是一樣的為什么要再寫一個單線程的線程池方式呢?

 

 1 public class Plan3 {
 2     private SimpleTask task = new SimpleTask();
 3     private MyFactory factory = new MyFactory(task);
 4     public static void main(String[] args) {
 5         Plan3 plan = new Plan3();
 6         ExecutorService pool = Executors.newSingleThreadExecutor(plan.factory);
 7         pool.execute(plan.task);
 8         pool.shutdown();
 9     }
10     
11     class MyFactory implements ThreadFactory{
12         private SimpleTask task;
13         public MyFactory(SimpleTask task) {
14             super();
15             this.task = task;
16         }
17         @Override
18         public Thread newThread(Runnable r) {
19             Thread thread = new Thread(r);
20             thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
21                 @Override
22                 public void uncaughtException(Thread t, Throwable e) {
23                     ExecutorService pool = Executors.newSingleThreadExecutor(new MyFactory(task));
24                     pool.execute(task);
25                     pool.shutdown();
26                 }
27             });
28             return thread;
29         }
30     }
31     
32     class SimpleTask implements Runnable{
33         private int task = 10;
34         @Override
35         public void run() {
36             String threadName = Thread.currentThread().getName();
37             System.out.println(threadName+"--"+"啟動");
38             while(task>0){
39                 try {
40                     Thread.sleep(100);
41                 } catch (InterruptedException e) {
42                     e.printStackTrace();
43                 }
44                 if(System.currentTimeMillis()%3==0){
45                     throw new RuntimeException("模擬異常");
46                 }
47                 System.out.println(threadName+"--"+"執行task"+task);
48                 task--;
49             }
50             System.out.println(threadName+"--"+"正常終止");
51         }
52     }
53 }

 

結果輸出:

 1 Thread-0--啟動
 2 Thread-0--執行task10
 3 Thread-0--執行task9
 4 Thread-1--啟動
 5 Thread-1--執行task8
 6 Thread-2--啟動
 7 Thread-2--執行task7
 8 Thread-2--執行task6
 9 Thread-2--執行task5
10 Thread-2--執行task4
11 Thread-2--執行task3
12 Thread-2--執行task2
13 Thread-3--啟動
14 Thread-3--執行task1
15 Thread-3--正常終止

 

  由於這邊只是用單線程,所以發現和上面區別不大。不過也展示了線程池是如何捕獲線程異常的。

  現在我們看看場景二定時任務,為什么我要寫一份單線程池的捕獲異常方式,就是用於和下面做對比。

  定時任務我們常常用ScheduledExecutorService,和上述ExecutorService獲取方式一樣。但是如果我們參照上述方式寫定時任務,然后獲取異常。我們會發現我們無法在uncaughtException方法內獲取到線程的異常。異常消失了,或者說線程發生異常根本就沒調用uncaughtException方法。

  后來查看相關API,發現在ScheduledExecutorService獲取異常的方式可以使用ScheduledFuture對象來獲取具體方式如下:

 

 1 public class Plan2 {
 2     private SimpleTask task = new SimpleTask();
 3     public static void main(String[] args) {
 4         Plan2 plan = new Plan2();
 5         start(plan.task);
 6     }
 7     
 8     public static void start(SimpleTask task){
 9         ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
10         ScheduledFuture<?> future = pool.scheduleAtFixedRate(task, 0, 1000, TimeUnit.MILLISECONDS);
11         try {
12             future.get();
13         } catch (InterruptedException | ExecutionException e) {
14             System.out.println(e.getMessage());
15             start(task);
16         }finally {
17             pool.shutdown();
18         }
19     }
20     
21     class SimpleTask implements Runnable{
22         private volatile int count = 0;
23         @Override
24         public void run() {
25             String threadName = Thread.currentThread().getName();
26             System.out.println(threadName+"--"+"啟動");
27             try {
28                 Thread.sleep(100);
29             } catch (InterruptedException e) {
30                 e.printStackTrace();
31             }
32             if(System.currentTimeMillis()%3==0){
33                 throw new RuntimeException("模擬異常");
34             }
35             System.out.println(threadName+"--"+"執行task"+count);
36             count++;
37             System.out.println(threadName+"--"+"正常終止");
38         }
39     }
40 }

 

 

結果輸出:

 

 1 pool-1-thread-1--啟動
 2 java.lang.RuntimeException: 模擬異常
 3 pool-2-thread-1--啟動
 4 pool-2-thread-1--執行task0
 5 pool-2-thread-1--正常終止
 6 pool-2-thread-1--啟動
 7 pool-2-thread-1--執行task1
 8 pool-2-thread-1--正常終止
 9 pool-2-thread-1--啟動
10 pool-2-thread-1--執行task2
11 pool-2-thread-1--正常終止
12 pool-2-thread-1--啟動
13 java.lang.RuntimeException: 模擬異常
14 pool-3-thread-1--啟動
15 pool-3-thread-1--執行task3
16 pool-3-thread-1--正常終止
17 pool-3-thread-1--啟動
18 java.lang.RuntimeException: 模擬異常
19 pool-4-thread-1--啟動
20 pool-4-thread-1--執行task4
21 pool-4-thread-1--正常終止
22 .....

 

至此我們實現了就算定時任務發生異常,總有一個線程會去執行。一個線程倒下,會有后續線程補上。

這里我只列了這三種關於線程任務異常終止,如果自動重啟任務。如果大家還有什么好方法,可以分享給我。謝謝!


 

 

 

 

  本文為博主原創文章,未經博主允許不得轉載。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM