本文为博主原创文章,未经博主允许不得转载。
我们开发工程中经常使用到线程,在线程使用上,我们可能会有这样的场景:
- 伴随这一个业务产生一个比较耗时的任务,而这个业务返回并不需要等待该任务。那我们往往会启动一个线程去完成这个异步任务。
- 我们需要一个定时任务比如:定时清除数据,我们会起一个定时执行线程去做该任务。
上述问题比较简单,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 .....
至此我们实现了就算定时任务发生异常,总有一个线程会去执行。一个线程倒下,会有后续线程补上。
这里我只列了这三种关于线程任务异常终止,如果自动重启任务。如果大家还有什么好方法,可以分享给我。谢谢!
本文为博主原创文章,未经博主允许不得转载。