线程池中实际运行的是线程池自身的线程,只是在runWorker方法中调用了我们传递进入Runnable对象的run()方法,那么如果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(); } 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; } finally { processWorkerExit(w, completedAbruptly); } }
在代码中我们看到,在调用用户Runnable实例方法run()的时候,进行了try…catch…finally,但是在catch()中是直接将异常抛出了,也就是说并未在while循环内消化掉,而是抛出给外层,这时会将while循环终止掉,然后在外层的try…finally中并未捕获内部传出的异常,所以异常信息会继续往上抛出,我们来关注一下这两层try的finally代码块,内部的finally中执行了一个空的方法afterExecute(),这个方法是留给我们自定义线程池时使用的,和beforeExecute()方法一样,既然是空方法,那我们就先不用去看它了,来看下外层的finally代码块
private void processWorkerExit(Worker w, boolean completedAbruptly) { // 从runWorker方法中传过来的是true,所以这句目前版本中必定会被执行到 // 作用是将当前线程池中的有效线程数-1,意思也就是出现异常的线程会被从线程池中拿掉 // 为什么说是出现异常的线程会被拿掉呢?因为在try内部是一个while循环,除非关闭核心线程或运行中线程出现异常,否则不会执行到这里 if (completedAbruptly) decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 更新完成的任务数,只要是被线程池线程执行过的,不管是否出现异常,都被认为是执行成功的任务 completedTaskCount += w.completedTasks; // 将当前Worker线程从线程池中移除销毁 workers.remove(w); } finally { mainLock.unlock(); } tryTerminate(); // 一系列判断,主要是判断是否符合给线程池创建新的线程 int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; } // 给线程池创建新的线程,core之所以传递false,是因为这里要防止创建失败 addWorker(null, false); } }
通过源码我们看到在处理任务的过程中,如果线程出现异常,则会将该线程从线程池中移除销毁,然后再新创建一个线程加入到线程池中,也就是说在任务发生异常的时候,会终结掉运行它的线程。
我们从源码中得到的信息,现在来验证一下我们的分析
验证代码:
public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30)); for (int i = 0; i < 10; i++) { int finalI = i; Thread t = new Thread(() -> { System.out.println(10 / finalI - 5); }); pool.execute(t); } }
输出结果:
抛异常了,但是并未影响线程池中的其他任务,我们打断点在processWorkerExit()方法中,看下workers变量的数据
异常发生之前
异常发生之后
看到在异常前后,线程1f36e637被移除了,转而创建了一个7073cb62放到了线程池中,而未发生异常的线程578486a3依然存在于线程池中。
小结
通过示例我们验证了一点:当任务出现未被捕获到的异常时,会将执行该任务的线程池中的线程从线程池移除并结束掉,然后移除之后创建一个新的线程放回到线程池中。
上面我们知道了当线程执行的任务发生未被捕获的异常时,会将异常一直往上抛出,那么我们能否在主线程中捕获它进行处理呢?我们来试下
public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30)); for (int i = 0; i < 10; i++) { int finalI = i; Thread t = new Thread(() -> { System.out.println(10 / finalI - 5); }); try { pool.execute(t); } catch (Exception e) { System.out.println("发生了异常"); } } }
运行结果
由运行结果可以看出我们并未捕获到线程池中线程抛出的异常,也就是异常并未被抛出到主线程中,这就尴尬了,毕竟这些异常是和业务相关联的,我们却无法捕获和处理,这咋整呢?
忽的一下,想到了线程池的比较重要的一个参数:ThreadFactory接口,这个接口的作用是按需创建新线程的,使用线程工厂消除了对Thread#Thread(Runnable) new Thread的强依赖,使应用程序能够使用特殊的Thread子类、优先级等。大白话就是让线程池中的线程使用我们自定义的线程,这个自定义可不是我们通过execute()或submit()传进来的自定义线程,而是Worker类中的thread变量,也就是实际运行的线程,我们看一下Worker类的构造方法
Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; // 调用ThreadFactory的newThread方法创建线程 this.thread = getThreadFactory().newThread(this); } 在构造方法中调用线程工厂的newThread()方法创建运行线程,我们上面通过ThreadPoolExecutor的构造方法创建线程池时并未传入ThreadFactory参数,那么就会使用默认的Executors.defaultThreadFactory()来创建线程,它的实现逻辑如下: public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; }
那么我们要是想自定义Worker#thread的值的话,就自定义一个ThreadFactory实现类即可,比如我们可以把线程池创建语句升级为:
public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30), r -> { Thread t = new Thread(r); t.setUncaughtExceptionHandler((t1, e) -> System.out.println("发生了异常")); return t; }); for (int i = 0; i < 10; i++) { int finalI = i; Thread t = new Thread(() -> { System.out.println(10 / finalI - 5); }); pool.execute(t); } }
我们使用lambda表达式来创建,使用Thread#setUncaughtExceptionHandler()方法来获取线程内未被捕获的异常,我们运行一下看看结果:
成功捕获了线程内部出现的异常。
那么现在就又有一个问题了:如果我们主动捕获并处理线程内抛出的异常,那么这个线程还会从线程池中移除销毁吗?
我们来试下,还是使用上面的那段代码,然后断点打在processWorkerExit()方法中,看下执行结果
异常之前
异常之后
从执行结果来看,发生异常的线程是35d176f7,在异常发生之后同样从线程池中被移除了。
总结
当线程池中线程执行任务的时候,任务出现未被捕获的异常的情况下,线程池会将允许该任务的线程从池中移除并销毁,且同时会创建一个新的线程加入到线程池中;可以通过ThreadFactory自定义线程并捕获线程内抛出的异常,也就是说甭管我们是否去捕获和处理线程池中工作线程抛出的异常,这个线程都会从线程池中被移除。
原文链接:
https://blog.csdn.net/luxiaoruo/article/details/106637384