ExecutorService中对异常的处理

您所在的位置:网站首页 获取您的捕获时出现问题怎么处理 ExecutorService中对异常的处理

ExecutorService中对异常的处理

2024-07-14 07:29| 来源: 网络整理| 查看: 265

ExecutorService加强了Executor这个接口,并提供了submit方法以加强Executor中的execute方法,也正是因为这两个方法某些细微的差异,造成了对异常处理时两个方法的千差万别。 在面对异常时,有如下几种场景

Thread1.自定义UncaughtExceptionHandler 2.用 try/catch代码块包围3.自己实现Thread的子类去记录异常日志ThreadPoolExecutor1.重写afterExecute()方法来记录异常 2.检查FutureFuture/ FutureTask1.调用get方法以获取内部的执行异常 2.重写 FutureTask.done()方法

综上来看,异常处理无非三种方式,要么用try/catch,要么自定义个handler,要么去弄个子类覆盖某些方法,我们今天就来探索探索这里面的坑。

先用一段非常简单的代码来体会下execute和submit对于异常处理的不同

public static void main(String[] args) throws Exception { ExecutorService exe = Executors.newFixedThreadPool(1); //exe.execute(new Task()); exe.submit(new Task()); public static class Task implements Runnable { @Override public void run() { System.out.println(Integer.parseInt("123")); System.out.println(Integer.parseInt("XYZ"));//这里会抛RuntimeException } } }

启动main方法,输出如下(注意!没有异常!) submit木有异常! 在这里插入图片描述 接着注释掉submit,启用execute,则会抛出异常

输出123后抛出异常 在这里插入图片描述

我们再修改下main方法

public static void main(String[] args) throws Exception { ExecutorService exe = Executors.newFixedThreadPool(1); Future f=exe.submit(new Task()); f.get(); }

这样修改之后,程序也像execute方法那样抛出了异常,这也是这两者之间的差别,submit方法会把异常也当作是任务的一部分,只有调用get了才能拿得到异常。

为什么submit会“吞下”异常直至调用他的get方法呢,我把代码贴出来,就一目了然了。 我们所调用的submit方法,是在AbstractExecutorService中实现的

public Future submit(Callable task) { if (task == null) throw new NullPointerException(); RunnableFuture ftask = newTaskFor(task);//将任务包装一下 execute(ftask); //执行任务 return ftask; }

execute方法是在ThreadPoolExecutor中实现的,整个execute方法其实就是判断线程池的状态然后选择到底是new新线程执行还是加入队列等等,干事的就是这一句addWorker(command, true); 之后就会调用内部类Worker的run方法, 在这里插入图片描述 我们具体看下这个方法

final void runWorker(Worker w) {//省略了一些状态判断的代码 Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); //此处调用FutureTask.run() } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; }

接着FutureTask.run()

public void run() {//省略了一些状态判断的代码 try { Callable c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); //这里吞下了异常!!!! } if (ran) set(result); } } finally { //省略了其他代码 } }

这样,异常就被submit给含在嘴里了,要他吐出来,只能去调用他的get方法

public V get() throws InterruptedException, ExecutionException { int s = state; if (s = CANCELLED) throw new CancellationException(); throw new ExecutionException((Throwable)x);//异常直到这里才会抛出 }

而如果是execute方法的话,task.run调用的就是Callable或者Runnable的方法了,所以有异常就直接抛了,没有了那FutureTask那层包装,所以异常最终给了afterExecute(task, thrown)这个接收函数。 因此对于线程池采用execute()方法来执行线程任务时,要想捕获未处理的异常: (1)可以重写afterExecute()方法来实现:

public class ThreadStudy2 { public static class ExceptionCaughtThreadPool extends ThreadPoolExecutor { List exceptions=new LinkedList(); public ExceptionCaughtThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue){ super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue); } @Override protected void afterExecute(Runnable r, Throwable t) {//通过重写线程池的afterExecute函数也能捕获execute执行任务时的异常 if (t!=null){ exceptions.add(t); System.out.println("catch exception in afterExecute"); return; } System.out.println("everything is ok"); } } public static void main(String[] args){ ExceptionCaughtThreadPool threadPool=new ExceptionCaughtThreadPool(1,1,100, TimeUnit.SECONDS,new LinkedBlockingQueue()); threadPool.execute(new Runnable() { @Override public void run() { throw new RuntimeException(); } }); System.out.println("--------DONE-----------"); } }

(2)实现UncaughtExceptionHandler;如果你没有实现一个handler,那么他就使用默认的handler来处理异常:

public class ThreadStudy { public static void main(String[] args) { final Thread.UncaughtExceptionHandler exceptionHandler = new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { synchronized (this) { System.err.println("Uncaught exception in thread '" + t.getName() + "': " + e.getMessage()); } } }; // 自定义线程的newThread方法以加入自己的Handler ThreadFactory threadFactory = new ThreadFactory() { @Override public Thread newThread(Runnable r) { System.out.println("creating pooled thread"); final Thread thread = new Thread(r); //以为这样可以抓住执行过程的异常 thread.setUncaughtExceptionHandler(exceptionHandler); return thread; } }; ExecutorService threadPool = Executors.newFixedThreadPool(1, threadFactory); // Callable callable = new Callable() { // @Override // public Integer call() throws Exception { // throw new Exception("Error in Callable"); // } // }; // threadPool.submit(callable);//submit执行时不能捕获UncaughtExceptionHandler异常 // threadPool.shutdown(); Runnable runnable = new Runnable() { @Override public void run() { System.out.println("执行任务"); int num = Integer.parseInt("TT"); } }; threadPool.execute(runnable);//execute执行时能捕获未处理的异常 System.out.println("-----------Done.---------"); } }

但是对于submit来说,异常是绑定到Future上了,但是调用future.get()的时候,这些异常才会给你抛出来,意味着你自己定义的handler其实是无效的;

结论: 如果我们关心线程池执行的结果,则需要使用submit来提交task,那么在afterExecute中对异常的处理也需要通过Future接口调用get方法去取结果,才能拿到异常,如果我们不关心这个任务的结果,可以直接使用ExecutorService中的execute方法(实际是继承Executor接口)来直接去执行任务,这样的话,我们的Runnable没有经过多余的封装,在runWorker中得到的异常也直接能在afterExecute中捕捉。

参考文档: https://www.cnblogs.com/wscit/p/6100476.html 这篇文章讨论通过自定义线程工程类,设置UncaughtException对象来捕获执行execute方法时抛出的异常 https://www.jianshu.com/p/d7d0a32cf028 这篇文章中对于复写afterExecute方法处理异常的描述不正确,是可以获取到异常信息的。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3