线程池异常处理详解,一文搞懂!

在实际开发中,我们常常会用到线程池,但任务一旦提交到线程池之后,如果发生异常之后,怎么处理? 怎么获取到异常信息?而不是任务提交之后,消失的无影无踪。

要知道以上答案,先看下 线程池里面的线程发生异常之后会发生什么。

我们先通过工具类Executors创建一个简单的线程池,里面核心线程数为1

 ExecutorService executorService=Executors.newFixedThreadPool(1);
  
   executorService.submit(()->{
            int i=1/0;
        });


  executorService.submit(()->{
            System.out.println("当线程池抛出异常后继续新的任务");
        });

上面我们创建了一个核心线程数和最大线程数都为1的线程池。
然后往里面提交了两个任务。
其中一个任务 int i=1/0; 0不能作为除数,因此会抛出异常,java线程中 如果抛出未被捕获的异常,会导致线程终止。

该线程池中只有一个线程,如果终止之后,提交第二个任务会发生什么?
运行结果

 

答案是第一个线程内部发生异常之后,没有任何异常信息出现,第二个任务正常执行。

线程池有两种提交方式 excute和sumbit ,换成excute 看下

ExecutorService executorService=Executors.newFixedThreadPool(1);

        executorService.execute(()->{
            int i=1/0;
        });

        executorService.execute(()->{
            System.out.println("当线程池抛出异常后继续新的任务");
        });

 

第一个任务出现了异常栈信息,第二个任务正常执行。

线程池的两种不同的提交方式,会有不同的异常情形,但是不管怎样,线程内的任务抛出异常之后,线程池照样能正常运行。

问题1:execute和submit有什么区别?为什么一个会抛出异常 一个不会?

这个问题先放这,我们等下回头再看,这个问题非常重要。

那么当前最要紧的问题来了,我们要如何才能获取到线程池里面的任务抛出的异常?
解决方案一,将整个任务try-catch起来,捕获里面的异常,这种方式是最简单有效的方式。

executorService.execute(()->{
            try{
                int i=1/0;
            }catch (Exception ex){
                System.out.println(ex.getMessage());
            }
        });

 换成submit提交

executorService.submit(()->{
            try{
                int i=1/0;
            }catch (Exception ex){
                System.out.println("sumbit提交"+ex.getMessage());
            }
        });

        executorService.submit(()->{
            System.out.println("当线程池抛出异常后继续新的任务");
        });

 

 

可以看到 清晰易懂的捕获到了异常,可以知道我们的任务出现了问题,而不是消失的无影无踪。

解析方案2: 每一个任务都加一个try-catch 实在是太麻烦了,而且代码也不好看,那么这样想的话,可以用UncaughtExceptionHandler 这个类。

 

 

UncaughtExceptionHandler 是Thread类一个内部类,也是一个函数式接口。
内部的uncaughtException是一个处理线程内发生的异常的方法,参数为线程对象t和异常对象e。

使用方式如下

//创建线程对象 内部会抛出异常
   Thread thread=new Thread(()->{
            int i=1/0;
        });

     //设置该对象的默认异常处理器
        thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e)->{
            System.out.println("exceptionHandler"+e.getMessage());
         });
//启动线程
        thread.start();

运行结果

 

 相反,如果我们不设置UncaughtExceptionHandler ,那么就是

Thread thread=new Thread(()->{
            int i=1/0;
        });

        thread.start();

 

 

直接抛出异常。

因此 Thread的UncaughtExceptionHandler类能帮我们捕获异常并处理, 那么在线程池里面生效吗?

试试excute提交

ExecutorService executorService=Executors.newFixedThreadPool(1);

        Thread thread=new Thread(()->{
            int i=1/0;
        });


        thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e)->{
            System.out.println("exceptionHandler"+e.getMessage());
         });

   

      executorService.execute(thread);

 

 

在excute提交方式里面是生效的。
那么因此,如果我们不想在每个线程的任务里面都加try-catch的话,可以自己实现的一个线程池,重写它的线程工厂方法,在创建线程的时候,都赋予UncaughtExceptionHandler处理器对象。

具体代码如下

//1.实现一个自己的线程池工厂
        ThreadFactory factory = (Runnable r) -> {
            //创建一个线程
            Thread t = new Thread(r);
            //给创建的线程设置UncaughtExceptionHandler对象 里面实现异常的默认逻辑
            t.setDefaultUncaughtExceptionHandler((Thread thread1, Throwable e) -> {
                System.out.println("线程工厂设置的exceptionHandler" + e.getMessage());
            });
            return t;
        };

        //2.创建一个自己定义的线程池,使用自己定义的线程工厂
        ExecutorService service = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,new LinkedBlockingQueue(10),factory);

        //3.提交任务
        service.execute(()->{
            int i=1/0;
        });

 

 

测试发现,即使我们不用在thread里面try-catch 也能拿到异常信息了。

我们来看下setDefaultUncaughtExceptionHandler的原理是什么
是在什么时候调用的。

 

 

Thread类里面dispatchUncaughtException(Throwable e) 这个方法,调用了 getUncaughtExceptionHandler().uncaughtException(this, e); 获取到了我们设置的UncaughtExceptionHandler,并把线程对象和异常对象都作为参数传进去了。

这个方法,只能被JVM调用,将未捕获异常分派给该方法所在的处理程序
调用的节点,就是当Thread对象抛出了未被捕获的异常的时候。

了解到这里,是不是我们只需要在线程池的线程工厂里面给所有生产的线程都设置上这个处理器就好了吗? 答案当然不是!

刚刚我们一直用的是excute, 这次我们用sumbit看下,

//1.实现一个自己的线程池工厂
        ThreadFactory factory = (Runnable r) -> {
            //创建一个线程
            Thread t = new Thread(r);
            //给创建的线程设置UncaughtExceptionHandler对象 里面实现异常的默认逻辑
            t.setDefaultUncaughtExceptionHandler((Thread thread1, Throwable e) -> {
                System.out.println("线程工厂设置的exceptionHandler" + e.getMessage());
            });
            return t;
        };

        //2.创建一个自己定义的线程池,使用自己定义的线程工厂
        ExecutorService service = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,new LinkedBlockingQueue(10),factory);

        //3. submit  提交任务
        service.submit(()->{
            int i=1/0;
        });

 

 

结果是什么也没有输出,异常信息消失了。 说明UncaughtExceptionHandler并没有被调用。
这样就回到了我们之前的
问题1:execute和submit有什么区别?

在日常使用中,我们知道,execute和submit最大的区别就是execute没有返回值,submit有返回值。
我们submit返回的是一个future ,可以通过这个future取到线程执行的结果或者异常信息。

 

 

 ExecutorService executorService = Executors.newFixedThreadPool(1);
        //创建Callable对象
        Callable callable=()->{return 1;};
        //提交Callable进线程池,返回future
        Future future = executorService.submit(callable);
        
        try {
            //获取线程池里面的结果
           Integer a= (Integer) future.get();
            System.out.println("future中获取结果"+a);
        } catch (Exception e) {
            //获取线程池里面的异常
            System.out.println("future中获取异常"+e.getMessage());
        }

 

 

注意,如果要获取返回的结果的话,这里的线程池提交的参数是Callable类型,而不是Thread或者Runable 。

那么获取异常

 ExecutorService executorService = Executors.newFixedThreadPool(1);
        //创建Callable对象
        //会抛出异常
        Callable callable=()->{return 1/0;};
        //提交Callable进线程池,返回future
        Future future = executorService.submit(callable);

        try {
            //获取线程池里面的结果
           Integer a= (Integer) future.get();
            System.out.println("future中获取结果"+a);
        } catch (Exception e) {
            //获取线程池里面的异常
            System.out.println("future中获取异常"+e.getMessage());
        }

 

 

有没有返回值是submit和excute最大的区别。 那么为什么 如果有返回值的submit ,里面的线程内的任务抛出未捕获的异常的时候,不会显示异常呢?

猜测是submit方法内部已经捕获了异常, 只是没有打印出来,也因为异常已经被捕获,因此jvm也就不会去调用Thread的UncaughtExceptionHandler去处理异常。

接下来验证猜测:

先看excute, 其是Executor的接口

 

 

再看submit

其是ExecutorService的接口,且ExecutorService继承自Executor

 

 

 

 然后!!重点!!
AbstractExecutorService 实现了ExecutorService接口,
抽象的AbstractExecutorService 类几乎实现了ExecutorService接口的所有方法
包括submit(Runable task)

 

 

这里可以看到,submit内部 也是调用了execute 。
调用之前创建了一个runableFuture对象,而且将这future对象作为参数,调用execute(runable r ), 而且调用完execute之后 返回了这个future 作为返回值。

通过下面可以看到,runableFuture同时继承了runable和future

 

 

因此runableFuture即是runable 也是future ,因此可以作为execute(runable r)的参数。

这里补充一点:
java中类是不允许多继承的,但是接口可以,
因为类的多继承会有问题,比如说类3继承了类1和类2, 类1和类2都有方法名为A的方法,但是其内部的实现逻辑不同,那么类3到底是继承的是谁的方法逻辑呢?
但是接口不一样,因为接口的方法都是声明没有方法体, 接口3,继承接口1和接口2的方法A, 方法A也只是一个方法声明,没有具体实现,不存在上述的歧义问题。

问题到这里面就明显了,为什么submit有返回值了 ,大体逻辑如下

//创建一个即是Runnable又是Future的对象
  RunnableFuture<Void> ftask = newTaskFor(task, null);
  //execute内部执行这个对象内部的逻辑,然后将结果或者异常 set到这个ftask里面
        execute(ftask);
        //返回这个ftask
        return ftask;

ThreadPoolExecutor继承了AbstractExecutorService ,实现了里面的execute方法。
同时作为Executors这个jdk自带的线程池工具类里面创建线程必不可少的一个组件, 同时也是我们自己定义自己的线程池必不可少的一个基础类。

我们之前的猜测是submit方法内部已经捕获了异常, 只是没有打印出来,也因为异常已经被捕获,因此jvm也就不会去调用Thread的UncaughtExceptionHandler去处理异常。

而submit里面也只是调用了execute,因此问题就出在execute内部了。

接下来分析execute,这涉及到了线程池内部的原理了, 相当深入。

我们看下 execute的实现

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
            //获取当前线程数
        int c = ctl.get();
        //如果当前线数小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
        **//新增一个worker**
            **if (addWorker(command, true))**
                return;
            c = ctl.get();
        }
 
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

忽略其他的,重点关注,在线程池的excute里面,我们的任务被提交到了addWorker(command, true) 。

看下addWorker的实现, 在ThreadPoolExecutor的内部,有一个内部类叫Worker
addWorker为其的一个方法,作用是将runable封装成Worker

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable {

//....省略其他

 private boolean addWorker(Runnable firstTask, boolean core) {
 //...省略其他
  boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        //将firstTask 参数封装成Worker
            **w = new Worker(firstTask);**
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                   
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                            //将worker加到线程池的队列中
                        **workers.add(w);**
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                //启动线程池中的一个线程 
                    **t.start();**
                    workerStarted = true;
                }
            }
}
}

Worker的构造函数

Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            //从线程池的线程工厂里面中创建出来一个线程
            this.thread = getThreadFactory().newThread(this);
        }

因此,任务被封装了一个worker,而worker实现了runable接口,因此执行的逻辑就在worker的run方法里面,里面调用了runWorker

 

 

final void runWorker(Worker w) {
//当前线程
        Thread wt = Thread.currentThread();
        //我们的提交的任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                    //直接就调用了task的run方法 
                        task.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和异常
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

核心就在 task.run(); 这个方法里面了, 期间如果发生异常会被抛出。

因此,如果用execute提交的任务,会被封装成了一个runable任务,然后进去 再被封装成一个worker,最后在worker的run方法里面跑runWoker方法, 里面再又调了我们最初的参数 runable任务的任务,并且用try-catch捕获了异常,会被直接抛出去,因此我们在execute中看到了我们的任务的异常信息。

那么为什么submit没有异常信息呢? 因为submit是将任务封装成了一个futureTask ,
然后这个futureTask被封装成worker,在woker的run方法里面,最终调用的是futureTask的run方法, 猜测里面是直接吞掉了异常,并没有抛出异常,因此在worker的runWorker方法里面无法捕获到异常。

excute最终是在ThreadPoolExecutor才会真正的实现, 但是submit在abstractExecutorService就实现了,
其内容如下

 

 newTaskFor方法将Runnable封装成了一个future

 

 然后再看futureTask的run方法,果不其然,生吞了异常,将异常放到了 setException(ex);里面

 public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        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 {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

setException(ex);
将异常对象赋予outcome

protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

outcome是futureTask的返回结果
调用futuretask的get的时候,返回report()

 

 reoport里面实际上返回的是outcome ,刚好之前的异常就set到了这个outcome里面

 

 

因此,在用submit提交的时候,runable对象被封装成了future ,里面的 run try-catch了所有的异常,并设置到了outcome里面, 可以通过future.get获取到outcome。

所以在submit提交的时候,里面发生了异常, 是不会有任何抛出信息的。

那么在submit里面,除了从返回结果里面取到异常之外, 没有其他方法了。
因此,在不需要返回结果的情况下,最好用execute ,这样如果疏漏了异常捕获,也不至于丢掉异常信息。

在excute的方法里面,可以通过重写afterExecute进行异常处理,但是注意! 这个也只适用于excute提交,因为submit的task.run里面把异常吞了,根本不会跑出来异常,因此也不会有异常进入到afterExecute里面,里面的thrown参数为null。

在runWorker里面,调用task.run之后,会调用线程池的 afterExecute(task, thrown) 方法

final void runWorker(Worker w) {
//当前线程
        Thread wt = Thread.currentThread();
        //我们的提交的任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                    //直接就调用了task的run方法 
                        task.run(); //如果是futuretask的run,里面是吞掉了异常,不会有异常抛出,
                       // 因此Throwable thrown = null;  也不会进入到catch里面
                    } 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和异常
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

因此重写 afterExecute(task, thrown); 这个方法,在里面也可以处理异常
在ThreadPoolExecutor里面 afterExecute 方法内没有任何逻辑

 

 代码例子:

//1.创建一个自己定义的线程池,重写afterExecute方法
                ExecutorService service = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,new LinkedBlockingQueue(10)){
                    @Override
                    protected void afterExecute(Runnable r, Throwable t) {
                        super.afterExecute(r, t);
                        System.out.println("afterExecute里面获取到异常信息"+t.getMessage());
                    }
                };

                //2.提交任务
                service.execute(()->{
                    int i=1/0;
                });

 

 如果要用这个afterExecute处理submit提交的异常, 要额外处理,因为用submit提交的时候,里面的Throwable对象为null,是、 如果要取异常信息,需要在Runnable r里面取,此时这个r实际的类型是futureTask

//定义线程池
 ExecutorService service = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(10)) {
 
 //重写afterExecute方法
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                if (t != null) { //这个是excute提交的时候
                    System.out.println("afterExecute里面获取到异常信息" + t.getMessage());
                }

//如果r的实际类型是FutureTask 那么是submit提交的,所以可以在里面get到异常 
                if (r instanceof FutureTask) {
                    try {
                        Future<?> future = (Future<?>) r;
                        future.get();
                    } catch (Exception e) {
                        log.error("future里面取执行异常", e);
                    }
                }
            }
        };

        //2.提交任务
        service.submit(() -> {
            int i = 1 / 0;
        });

 

posted @ 2019-10-07 11:55  小推爱学习  阅读(13689)  评论(4编辑  收藏  举报