线程任务异常终止问题

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



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

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

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

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



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

时间: 2024-11-10 13:16:55

线程任务异常终止问题的相关文章

Java并发程序设计(7)线程池之异常终止和正常关闭

1.1. 线程池中的线程的异常终止 如果线程池中的线程的任务代码发生异常导致线程终止,线程池会自动创建一个新线程. 对于各种类型的线程池,都是如此.以下代码在单个线程的线程池中抛出一个异常,可以发现后续任务中输出的每个tid的值都不相同. ExecutorService executorService = Executors.newSingleThreadExecutor(); for(int j=0;j<10;j++){ final int t = j; executorService.exe

Java多线程系列--“JUC线程池”03之 线程池原理(二)

线程池示例 在分析线程池之前,先看一个简单的线程池示例. import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class ThreadPoolDemo1 { public static void main(String[] args) { // 创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.newFixedThre

java多线程系类:JUC线程池:03之线程池原理(二)(转)

概要 在前面一章"Java多线程系列--"JUC线程池"02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代码(基于JDK1.7.0_40)线程池源码分析(一) 创建"线程池"(二) 添加任务到"线程池"(三) 关闭"线程池" 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509954.h

Java线程池的几种实现 及 常见问题讲解

工作中,经常会涉及到线程.比如有些任务,经常会交与线程去异步执行.抑或服务端程序为每个请求单独建立一个线程处理任务.线程之外的,比如我们用的数据库连接.这些创建销毁或者打开关闭的操作,非常影响系统性能.所以,“池”的用处就凸显出来了. 1. 为什么要使用线程池 在3.6.1节介绍的实现方式中,对每个客户都分配一个新的工作线程.当工作线程与客户通信结束,这个线程就被销毁.这种实现方式有以下不足之处: 服务器创建和销毁工作的开销( 包括所花费的时间和系统资源 )很大.这一项不用解释,可以去查下"线程

Java - &quot;JUC线程池&quot; ThreadPoolExecutor原理解析

Java多线程系列--"JUC线程池"02之 线程池原理(一) ThreadPoolExecutor简介 ThreadPoolExecutor是线程池类.对于线程池,可以通俗的将它理解为"存放一定数量线程的一个线程集合.线程池允许若个线程同时允许,允许同时运行的线程数量就是线程池的容量:当添加的到线程池中的线程超过它的容量时,会有一部分线程阻塞等待.线程池会通过相应的调度策略和拒绝策略,对添加到线程池中的线程进行管理." ThreadPoolExecutor数据结构

深入理解java线程池—ThreadPoolExecutor

几句闲扯:首先,我想说java的线程池真的是很绕,以前一直都感觉新建几个线程一直不退出到底是怎么实现的,也就有了后来学习ThreadPoolExecutor源码.学习源码的过程中,最恶心的其实就是几种状态的转换了,这也是ThreadPoolExecutor的核心.花了将近小一周才大致的弄明白ThreadPoolExecutor的机制,遂记录下来. 线程池有多重要##### 线程是一个程序员一定会涉及到的一个概念,但是线程的创建和切换都是代价比较大的.所以,我们有没有一个好的方案能做到线程的复用呢

第78课 多线程中的信号与槽(上)

1. QThread类中的信号和槽 (1)QThread类拥有发射信号和定义槽函数的能力 (2)QThread中的关键信号 ①void started():线程开始运行时发射该信号 ②void finished():线程完成运行时发射该信号 ③void terminated():线程被异常终止时发射该信号 2. 与线程相关的概念 (1)线程栈 ①进程中存在栈空间的概念(区别于栈数据结构) ②栈空间专用于函数调用(保存函数参数.局部变量等) ③线程拥有独立的栈空间(可调用其它函数) ④只要函数体中

java网络编程serversocket

转载:http://www.blogjava.net/landon/archive/2013/07/24/401911.html Java网络编程精解笔记3:ServerSocket详解ServerSocket用法详解 1.C/S模式中,Server需要创建特定端口的ServerSocket.->其负责接收client连接请求. 2.线程池->包括一个工作队列和若干工作线程->工作线程不断的从工作队列中取出任务并执行.-->java.util.concurrent->线程池

人在江湖:如何用代码保护好自己(转)

现在上一点规模的系统,特别是金融行业的系统,业务规则复杂,一般是将系统分割成较小的子模块,每个人开发一个或几个模块,模块开发完成后做成一个jar包,供其它的模块调用,待所有模块开发完成后再集成在一起.对于充值系统而言则更为复杂,除了要将系统分解成子模块外,还要与众多外围系统交互,如收单服务商.充值中心.银行等.程序员就是其中一个或几个模块的开发者. 本文的讨论的要点是:在系统出现问题时,如何有理有据的保护好自己. 对于软件开发者来说,我们在公司里一般处于弱势群体,每当系统出现问题造成事故的时候,