线程池一种池化技术的实现,用于实现管理线程和线程的复用。避免频繁地创建和销毁线程的开销,以及控制并发执行的线程数量,从而提高系统的性能和资源利用率。
在线程池中会预先根据参数核心线程数来创建并且保留这些线程。当需要执行任务时,从线程池中获取一个空闲的线程,将任务分配给该线程执行。当任务执行完毕后,线程将返回到线程池,可以被其他任务复用。
ThreadPoolExecutor
ThreadPoolExecutor时线程池Excutor框架的最核心的类,提供了线程池的执行规范的实现,ThreadPoolExecutor 类中提供的四个构造方法来搞糟线程池 ,都是根据不同的参数来构建不同需求的线程池(线程池的7大核心参数),通过内部类来实现拒绝策略,以及还有一个特殊的 核心 Worker内部类。
用来计算 或者说是限制线程数 留3位来表示线程池的状态,源码提到线程数不满足需求时会调整位AtomicLong类型并调整下面的移位/掩码常量,但是目前5亿的线程数已经满足需求并且使用 int 的这段代码会更快、更简单一些
private static final int COUNT_BITS = Integer.SIZE - 3;
线程池的最大线程数量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
线程池的状态描述
private static final int RUNNING = -1 << COUNT_BITS;//线程池处于运行状态,能够接受新任务并处理队列中的任务。
private static final int SHUTDOWN = 0 << COUNT_BITS;
线程池已关闭,不再接受新任务,但会处理已提交的任务。
private static final int STOP = 1 << COUNT_BITS;
线程池已停止,不再接受新任务,并且会终止正在执行的任务。
private static final int TIDYING = 2 << COUNT_BITS;
线程池已完成所有任务,准备清理资源。
private static final int TERMINATED = 3 << COUNT_BITS;
线程池已完全终止,所有资源已释放。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
用于存储线程池的状态和工作线程的数量。设置初始值ctlOf(RUNNING, 0) ,表示线程池初始状态为 RUNNING,且当前没有工作线程
worker类是什么
worker是工作线程,主要用于记此worker正在运行的线程(线程池中存活的线程)和要运行的线程任务(第一个任务),还记录了每个线程完成的任务数量,而线程池内部也维护了一个set,存储了线程池中所有的线程。
线程池的工作流程
线程池在执行任务时 ,可以excute提交行线程任务由Excutor提供的规范,用于提交不需要返回值的任务,或者通过submit来提交任务,由ExecutorService提供的规范,用于执行需要返回值的任务。执行线程的过程为以下几个步骤:
提交任务
会去判断当前线程池是否有空闲线程或者是否能创建线程来执行当前任务,分为三个步骤
- 如果正在运行的线程少于 核心线程数,尝试启动一个新线程,并将传入的任务作为第一个任务。调用
addWorker会原子性地检查运行状态和工作线程数量,从而防止在不应该添加线程的情况下错误地添加线程,返回 false。 - 如果任务能够成功排队,那么我们仍然需要再次检查是否应该添加一个线程(因为在上次检查后,已有的线程可能已死亡),或者在进入此方法后线程池是否已关闭。因此,我们重新检查状态,如果必要的话,如果已停止,则回滚排队,或者如果没有线程,则启动一个新线程。
- 如果我们无法将任务排队,那么我们尝试添加新线程。如果失败,我们知道我们已经关闭或饱和,因此拒绝了任务。
addWorker方法:向线程池中添加一个新的工作线程,该线程将执行指定的任务。首先获取线程池的状态通过runStateOf(ctl.get()),判断线程池是否关闭,如果线程池正在关闭且队列中有任务,则返回 false。通过workerCountOf(ctl.get())来获取线程数 判断线程数是否大于最大线程数,小于的话,通过CAS操作 递增线程数,否则则返回 false。
然后尝试进行以下操作通过workerAdded标志位来判断是否成功,将线程池中的线程和提供的任务封装到worker。尝试获取worker中的线程并且使用ReentrantLock加锁,检查线程池是否关闭,调用线程的isAlive方法来判断线程是否在运行如果是则抛出异常 ,然后将改worker添加到WorkerSet中,跟新当前的工作线程数量,并且跟新当前最大的线程数。
如果添加成功则调用该线程来执行任务,并且将该线程标注为启动
如果失败则调用addWorkerFailed移除WorkerSet创建的worker,该操作由ReentrantLock保证同步操作。
执行线程任务
- 调用worker中的run方法,实质上是调用的runWorker方法。首先获取worker对象,获取工作线程和firstTask。然后加锁,
`循环执行任务,如果在执行时该worker在创建时没有绑定执行任务(提交任务时任务数小于最大核心线程数),则会去调用getTask()从任务队列中获取任务, 然后同步调用这个task的run()方法.检查线程池的状态,如果线程池停止了则中断当前所有在运行的线程,执行玩任务后跟新该工作线程完成任务的数量释放锁,调用processWorkerExit执行worker清理, 如果是异常退出,减少工作线程计数,正常进行 的话会去更新线程池已完成任务计数,从工作线程集合中移除该线程 然后去判断线程池的任务是否执行完了,尝试性的关闭线程池。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
省略部分代码
tryTerminate(); // 尝试终止线程池
int c = ctl.get(); // 获取当前控制状态
if (runStateLessThan(c, STOP)) { // 如果状态小于 STOP
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // 判断最小线程数
if (min == 0 && !workQueue.isEmpty())
min = 1; // 如果没有核心线程且工作队列不为空,设置最小线程数为 1
if (workerCountOf(c) >= min)
return; // 如果当前工作线程数大于等于最小值,则无需替换
}
addWorker(null, false); // 添加新的工作线程
}
}
实现线程的复用逻辑代码为
runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
当工作线程执行完一个任务后,它不会被销毁,而是通过 getTask()方法尝试获取下一个任务。
while (task != null || (task = getTask()) != null) {
try {
task.run(); // 执行任务
} catch (RuntimeException e) {
} finally {
task = null; // 清空任务引用
}
}
获取任务的机制
getTask方法在线程池的工作线程执行任务时当线程的任务执行完毕后会循环的去工作队列中获取先进入的任务,如果没有任务可执行时会进入等待状态,直到有新的任务被提交到任务队列,如果线程数是超出核心线程数时,会通设置的存活时间来清除线程。
数组实现的阻塞队列中获取任务的方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//如果队列为空阻塞
return dequeue();
} finally {
lock.unlock();
}
}
总结
线程池本质就是通过hashSet存储线程。多余的任务会放在阻塞队列中。只有当阻塞队列满了后,才会触发非核心线程的创建。直到空闲了,超过存活时间就销毁了。线程池提供了两个钩子(beforeExecute,afterExecute)给我们,我们继承线程池,在执行任务前后做一些事情。