张三和你一聊聊线程池

612 阅读5分钟

线程池是开发中常用的一个东西,很多同学对对线程池的原理和工作流程并不熟悉,今天就让张三和大家聊一聊他眼中的线程池长什么样吧。

线程池的好处

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池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刷题思路,有兴趣的同学赶快关注我吧

参考链接

  1. tech.meituan.com/2020/04/02/…
  2. blog.csdn.net/DreamWeaver…