zoukankan      html  css  js  c++  java
  • 线程任务异常终止问题

      本文为博主原创文章,未经博主允许不得转载。


      我们开发工程中经常使用到线程,在线程使用上,我们可能会有这样的场景:

    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 .....

    至此我们实现了就算定时任务发生异常,总有一个线程会去执行。一个线程倒下,会有后续线程补上。

    这里我只列了这三种关于线程任务异常终止,如果自动重启任务。如果大家还有什么好方法,可以分享给我。谢谢!


      本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    非常强大的table根据表头排序,点击表头名称,对其内容排序
    处理字符集中的算式问题
    java基础复习:final,static,以及String类
    try,catch,finally含return时的执行顺序及丢失的伪例
    系统 触发器
    某个表按某个列分组且按另一个列排序
    XML读取
    硬软件交互原理
    TcpClient 读写流
    C# Socket的TCP通讯 异步 (2015-11-07 10:07:19)转载▼
  • 原文地址:https://www.cnblogs.com/yuhuihong19941210/p/5547501.html
Copyright © 2011-2022 走看看