1. 优秀文章整理
1.1 一个线程池中的线程异常了,那么线程池会怎么处理这个线程?
1.2 讲述线程池原理,动态化线程池技术
1.3 讲述动态化线程池时可能遇到的一些坑
1.4 再谈动态化调整线程池
1.5 讲述线程池mainLock锁,为何使用非线程安全set保存线程,讲述worker继承AQS的意义
1.6 线程池如何让最大线程数用完,再让任务进入队列(自定义队列,重写其 offer 方法)
1.7 如何设计线程池:1000 多个并发线程,10 台机器,每台机器 4 核,设计线程池大小。
包括普通Java线程池和Tomcat内置线程池的运行流程
1.8 讲述线程回收的相关流程
只要任务产生的速度大于线程轮询执行的速度,那么多余coreSize的线程就不会被回收掉
1.7 主要学习一下策略模式,责任链模式,观察者模式
1.8 线程池如何知道一个线程的任务已经执行完成?
1)可以使用submit + Future.get()
2)使用CountDownLautch的await + countDown
总结:因为线程池中的任务执行本身是没有返回值的,所以要想知道任务的执行状态,就只能通过这种阻塞 + 唤醒的方式,方式1和2原理都是如此;
【Java面试】面试被问:线程池如何知道一个线程的任务已经执行完成?_哔哩哔哩_bilibili
1.9 为什么线程池的submit不抛出异常?
1)使用submit提交的任务,执行的call()方法时外面被try catch住了,然后把异常结果set到了outCome中,需要通过Future.get()才能拿到异常信息;所以,使用使用submit提交的任务,执行中抛出异常时,是不会在控制台把异常信息打印出来的;
大厂面试题:为什么线程池的submit不抛出异常?_哔哩哔哩_bilibili
2.0 多线程使用的3个坑
其中有一个小技巧:给线程池加一个简单的监控
2. 线程池原理篇
2.1 ThreadPoolExecutor运行流程
从运行流程,就可以看线程池的功能核心设计细分为几个模块,包括:
- 任务调度:任务分配
- 任务缓存:任务管理
- 任务申请:实现任务管理模块和线程管理模块的通信,对应源码的getTask()
- 线程管理:Worker线程,Worker线程增加,Worker线程回收
- 任务拒绝:线程池的保护部分,保护机制
2.2 线程管理模块
2.2.1 Worker线程
Worker执行任务的模型如下图所示:
firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。
2.2.2 Worker线程增加
增加线程是通过线程池中的addWorker方法,该方法的功能就是增加一个线程,该方法不考虑线程池是在哪个阶段增加的该线程,这个分配线程的策略是在上个步骤完成的,该步骤仅仅完成增加线程,并使它运行,最后返回是否成功这个结果。
addWorker方法有两个参数:firstTask、core。firstTask参数用于指定新增的线程执行的第一个任务,该参数可以为空;core参数为true,表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize,其执行流程如下图所示:
addWorker方法,会创建一个包含一个Thread线程的Worker,并把该线程start启动。并且该线程创建时,Worker自身会以this指针的形式传递给该Thread线程。Worker实现了Runnable,该Runnable内部的run() -> runWorker() -> getTask() -> task.run();
2.2.3 Worker线程回收
回收过程如下图所示:
线程回收的工作是在processWorkerExit()完成的。processWorkerExit()被runWorker()调用:
2.2.4 线程执行
在Worker类中的run方法调用了runWorker方法来执行任务,runWorker方法的执行过程如下:
-
while循环不断地通过getTask()方法获取任务。
-
getTask()方法从阻塞队列中取任务。
-
如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态。
-
执行任务。
-
如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程。