线程池中实际运行的是线程池自身的线程,只是在runWorker方法中调用了我们传递进入Runnable对象的run()方法,那么如果run()方法中出现异常了,那么要怎么处理?会不会将我们的线程池停掉?

我们先来看下runWorker()方法的具体逻辑:

final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean 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 runWorkerthis.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

线程池内运行的线程抛异常,线程池会怎么办相关推荐

  1. 线程池中运行的线程,当等待队列未满的情况下,一定不大于核心线程数吗

    通过<线程池内运行的线程抛异常,线程池会怎么办>了解到当线程执行的任务出现异常时,会将当前线程移出线程池,并新增一个线程到线程池中,我们先来回顾一下线程池的运行原理: 从原理图中可以看到只 ...

  2. python 中主线程结束 子线程还在运行么_「干货」python线程笔记

    引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条数据需要花费1秒,但读取数据只需要0.1秒,每条数据互不干扰.该如何执行才能花费时间最短呢? 在多线程(MT)编程出现之前 ...

  3. 写给大忙人看的进程和线程(内附思维导图)

    我们平常说的进程和线程更多的是基于编程语言的角度来说的,那么你真的了解什么是线程和进程吗?那么我们就从操作系统的角度来了解一下什么是进程和线程. 我们平常说的进程和线程更多的是基于编程语言的角度来说的 ...

  4. 多线程学习(四)-线程范围内共享数据

    一:线程范围内共享数据: 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做. 如果每个线程执行的代码不同,这时候需要 ...

  5. java future 线程 状态_手把手带你了解Java线程的实现方式及生命周期原理

    前言 我们在工作中线程技术很多情况下都能用的到,而且我们在面试的时候,线程技术基本上也是必问的.今天我来从线程的实现方式以及线程的生命周期做一个全面的讲解与分析,帮助大家能更好的去了解线程技术. 概念 ...

  6. java thread 线程销毁_手把手带你了解Java线程的实现方式及生命周期原理

    前言 我们在工作中线程技术很多情况下都能用的到,而且我们在面试的时候,线程技术基本上也是必问的.今天我来从线程的实现方式以及线程的生命周期做一个全面的讲解与分析,帮助大家能更好的去了解线程技术. 概念 ...

  7. python结束线程池正在运行的线程_python之线程与线程池

    #进程是资源分配的最小单位,线程是CPU调度的最小单位.每一个进程中至少有一个线程.#传统的不确切使用线程的程序称为只含有一个线程或单线程程序,而可以使用线程的程序被称为多线程程序,在程序中使用一个线 ...

  8. 自定义线程池内置线程池的使用 ThreadPoolExecutor和Executorservice 示例与注意事项

    文章目录 线程池介绍 自己设计一个线程池 1.设计ThreadPool类: 2.设计工作队列 3.实现自己设计的线程池 用java的ThreadPoolExecutor自定义线程池 自定义线程池-参数 ...

  9. 线程基础知识_Synchronized_ThreadAPI_自定义锁_获取线程运行时异常

    Synchronized synchronized包含monitor enter, monitor exit 2个JVM指令(遵循happens-before原则), 执行monitor exit之前 ...

最新文章

  1. Lintcode108 Palindrome Partitioning || solution 题解
  2. 视觉系统的演化之旅——视觉器官、光感受器及视觉分子
  3. 137.三网?哪三网?139.网络协议三要素?141.网络安全有哪些方面?
  4. hs300 quant
  5. 结合eShopOnWeb全面认识领域模型架构
  6. python声明编码格式_使用python将doc文件转为utf8编码格式的txt
  7. canvas的基本应用
  8. matlab实现k-l算法,Matlab实现动态规划算法 (dynamic programming algorithm)
  9. Python稳基修炼之计算机等级考试易错细节题4(含答案)
  10. php访问mysql数据库实验报告,php访问mysql数据库
  11. svn: 无法连接主机“127.0.0.1”: 拒绝连接
  12. 这次要说不明白 immutable 类,我就怎么地!
  13. linux查看数据库实例名端口号,查看数据库tns配置
  14. dateutil和pytz的安装
  15. 传苹果将采用:夏普IGZO技术面板量产
  16. 工业和信息化部教育与考试中心职业技术证书
  17. javaScript高级[二]
  18. python隐藏源码,生成pyd文件并调用的完整过程
  19. 云日记个人中心项目思路
  20. linux网卡恢复默认配置,Linux网卡的配置

热门文章

  1. STL教程(十): 关联容器--unordered_map/unordered_multimap
  2. 地图下载1之天地图瓦片解析
  3. devc 如何编写java,Android For JNI(1)——JNI的概念以及C语言开发工具dev-c++,编写你的第一个C语言程序,使用C启动JAVA程序...
  4. 【网络通信】【GNS3】Window10 下 GNS3 配置 IOU 模拟环境
  5. 如何远程连接Linux系统服务器
  6. 计算机算法发展史文献,多舛的创新历程:国内结构分析软件发展历程之高校篇...
  7. jmeter安装与介绍
  8. 解决百度爬虫无法爬取 Github Pages 个人博客的问题
  9. 【Java 基础】JDK API Documentation 教程使用详解
  10. NextVR踏入足球锦标赛直播 更能身历其境