ExecutorService中对异常的处理

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

Thread1.自定义UncaughtExceptionHandler 2.用 try/catch代码块包围3.自己实现Thread的子类去记录异常日志
ThreadPoolExecutor1.重写afterExecute()方法来记录异常 2.检查Future
Future/ 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 <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> 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<V> 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 <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);   //接着往下看
    }

private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        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<Throwable> exceptions=new LinkedList();
        public ExceptionCaughtThreadPool(int corePoolSize,
                                         int maximumPoolSize,
                                         long keepAliveTime,
                                         TimeUnit unit,
                                         BlockingQueue<Runnable> 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方法处理异常的描述不正确,是可以获取到异常信息的。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
当使用ExecutorService.submit()方法提交任务时,如果任务执行过程发生异常,可以通过以下几种方式来处理异常: 1. 使用try-catch块捕获异常:在提交任务时,将任务的执行逻辑放在try块,然后在catch块处理异常。这样可以确保异常不会向上传递到主线程,而是在任务内部被捕获和处理。 ```java ExecutorService executorService = Executors.newFixedThreadPool(5); Future<?> future = executorService.submit(() -> { try { // 任务执行逻辑 } catch (Exception e) { // 异常处理逻辑 } }); ``` 2. 使用Future对象的get()方法获取结果并处理异常:submit()方法返回一个Future对象,可以通过调用get()方法来获取任务的结果。在调用get()方法时,如果任务执行过程发生异常,get()方法会抛出ExecutionException异常,可以在catch块处理异常。 ```java ExecutorService executorService = Executors.newFixedThreadPool(5); Future<?> future = executorService.submit(() -> { // 任务执行逻辑 }); try { future.get(); } catch (ExecutionException e) { Throwable cause = e.getCause(); // 异常处理逻辑 } ``` 3. 使用CompletionService来获取已完成的任务结果并处理异常:CompletionService可以用于获取已完成的任务结果,并通过调用take()方法来获取结果。如果任务执行过程发生异常,可以在处理结果时捕获和处理异常。 ```java ExecutorService executorService = Executors.newFixedThreadPool(5); CompletionService<Void> completionService = new ExecutorCompletionService<>(executorService); completionService.submit(() -> { // 任务执行逻辑 return null; }); try { Future<Void> future = completionService.take(); future.get(); // 获取任务结果并处理异常 } catch (ExecutionException e) { Throwable cause = e.getCause(); // 异常处理逻辑 } ``` 以上是处理异常的几种常见方式,具体选择哪种方式取决于你的需求和代码结构。在处理异常时,建议根据具体情况选择合适的方式,并确保异常可以被正确捕获和处理,以确保程序的稳定性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值