线程池是开发中常用的一个东西,很多同学对对线程池的原理和工作流程并不熟悉,今天就让张三和大家聊一聊他眼中的线程池长什么样吧。
线程池的好处
- 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
线程池七大参数详解
线程池的核心构造函数包含了七个参数,我们来看一下他的代码实现
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//非法参数直接抛出异常
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
//线程池核心线程数
this.corePoolSize = corePoolSize;
//线程池最大线程数
this.maximumPoolSize = maximumPoolSize;
//阻塞队列类型
this.workQueue = workQueue;
//线程没有任务执行时最多保持多久时间会终止
this.keepAliveTime = unit.toNanos(keepAliveTime);
//线程工厂
this.threadFactory = threadFactory;
//拒绝策略
this.handler = handler;
}
- corePoolSize 核心线程数,可以把他理解为常驻线程,所有新来的任务都会先尝试是否有空闲的corePool线程可以获取,如果获取失败,则会进入阻塞队列进行等待
- maximumPoolSize 最大线程数,这个参数规定了线程池内最大的线程数,其中包含了corePool,当阻塞队列已满的情况下才会启用非core线程进行处理
- workQueue 阻塞队列类型,不同类型的阻塞队列会存在不同的特性,详细介绍参考下图
- keepAliveTime 时间长度,当非core线程在这个时间长度内没有被使用则会被回收
- unit 时间单位,可选天、小时、分钟、秒、毫秒、微秒、纳秒
- threadFactory ThreadFactory工厂能替代默认的new Thread(),在自定义工厂里面,我们能创建自定义化的Thread,并且计数,或则限制创建Thread的数量,给每个Thread设置对应的好听的名字,或则其他的很多很多事情。
- handler 拒绝策略,当线程池内不存在空闲线程且阻塞队列已满,则会按照相应的拒绝策略进行处理
线程池工作流程
一图胜千言,看图
线程池生命周期管理
线程池的状态是由线程池内部进行维护的,线程池内部一共维护两个值:运行状态和线程数量,这两个值同时保存在ctl这个变量中,ctl是一个原子类整形,高三位来保存线程池的运行状态,低29为来保存线程数量,由于线程池一共存在五种状态,因此只需要三位就可以体现不同的状态。用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。
线程管理
线程池内的线程是通过一个内部类Worker来进行封装,Worker类中的字段除了线程类,还包括一个firstTask,表示该线程的初始化任务,可以为null,当创建线程池后,如果Worker类中的firstTask字段不为null,则会先去执行firstTask,之后再正常工作
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
}
线程池Executor类中有一个HashSet类型的Workers字段,用来保存和持有线程的引用,这样可以通过添加或移除引用来控制线程的生命周期。
Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反应线程现在的执行状态。
lock方法一旦获取了独占锁,表示当前线程正在执行任务中。 如果正在执行任务,则不应该中断线程。 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断。 线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。
在线程回收过程中就使用到了这种特性,回收过程如下图所示:
结束语:本篇大量参考了美团技术团队与2020年的文章《Java线程池实现原理及其在美团业务中的实践》,本想今天转载过来,但由于权限原因无法转载,只好自己根据文章做了初步总结,原文放在了左下角阅读原文,里面对线程池做了更详细的讲解,有兴趣的同学可以去看一下
我的个人公众号是:码外狂徒。每周更新后端学习知识和LeetCode刷题思路,有兴趣的同学赶快关注我吧
参考链接