本文為博主原創文章,未經博主允許不得轉載。
我們開發工程中經常使用到線程,在線程使用上,我們可能會有這樣的場景:
- 伴隨這一個業務產生一個比較耗時的任務,而這個業務返回並不需要等待該任務。那我們往往會啟動一個線程去完成這個異步任務。
- 我們需要一個定時任務比如:定時清除數據,我們會起一個定時執行線程去做該任務。
上述問題比較簡單,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 .....
至此我們實現了就算定時任務發生異常,總有一個線程會去執行。一個線程倒下,會有后續線程補上。
這里我只列了這三種關於線程任務異常終止,如果自動重啟任務。如果大家還有什么好方法,可以分享給我。謝謝!
本文為博主原創文章,未經博主允許不得轉載。