图文并茂,从源码的角度,深入理解线程池原理(总体设计、线程池的生命周期、任务调度、Worker工作线程)

导图

image.png

写在前面

线程池是基于「池化」思想锁设计的线程管理和任务调度工具

好处

  • 降低资源消耗:重复利用已创建(池化)的线程,降低线程创建和销毁造成的性能损耗。
    • 如果 创建与销毁消耗的时间 > 任务执行时间,是很低效的。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。
    • 根据jvm规范,一个线程默认最大栈大小1M,这个栈空间是需要从系统内存中分配的。线程越多,消耗内存就越多。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

 

池化思想

池化思想在计算机领域中的表现为:统一管理服务器、存储、和网络资源等等。

比较典型的几种使用:

  • 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。
  • 实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。
  • 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。

 

解决的问题

解决的核心问题是资源管理问题。系统并不能确定任意时刻,需要多少线程执行多少任务。

  • 无法统一的管控资源(线程):可能使系统资源耗尽。
  • 频繁的申请和销毁资源:带来额外的时间损耗。
  • 无法系统的管理资源分配:没有统一的入口管理资源和任务。

 


总体设计

设计

image.png

  • Executor:顶层接口,将任务的提交和任务的执行解耦。用户只需要提交 Runnable 到 Executor,由 Executor 框架进行任务的调度与执行
  • ExecutorService:支持异步任务的提交(submit);支持停止线程池(shutdown/shutdownNow)
  • AbstractExecutorService:将任务执行流程串联起来(newTaskFor)
  • ThreadPoolExecutor:实现了最复杂的任务运行。ThreadPoolExecutor 一方面需要维护自身生命周期、领域方面需要同时管理线程和任务。

 

将任务提交和任务执行进行解耦

  • 线程池内部构件了一个 生产者-消费者 模型,将线程和任务解耦,可以达到任务缓冲、复用线程的目的。
    • 生产者:任务管理
    • 消费者:线程管理


生命周期管理

运行状态

ThreadPoolExecutor 共定义了五种运行状态

  • 其中,shutdown/stop 可通过 shutdown() 和 shutdownNow() 触发

 

生命周期

 

如何维护(记录)运行状态

线程池内部维护了一个变量 ctl 用来记录线程池的运行状态和池内有效线程数量。其中,高3位用于记录线程池的状态(runState);低29位用于就池内有效线程数量(workerCount)。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

1. 工作线程Worker会不断接收新任务去执行,而当工作线程Worker接收不到任务的时候,就会开始被回收。任务拒绝(拒绝策略)我们可以自定义拒绝策略,或使用 JDK 提供的四种拒绝策略

  • 自定义拒绝策略
public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
  • 默认拒绝策略(抛异常、丢弃任务、丢弃最老的、由提交线程自行处理任务)

 

任务缓冲(任务队列)

线程池中的生产者-消费者模式,是通过阻塞队列来实现的。

我们可以用不同的阻塞队列来实现不同的策略。


管理Worker工作线程

是什么

为了个更好的掌握线程的状态和维护线程的生命周期,线程池内部定义了工作线程Worker

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    final Thread thread;//Worker持有的线程
    Runnable firstTask;//初始化的任务,可以为null

    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
}
  • 任务执行流程示意图

当 addWorker 成功后,主线程就会拉起 worker 线程执行任务


 

新增Worker(addWorker)

该方法会新增一个工作线程并使他运行。

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (int c = ctl.get();;) {
        //1. 判断当前线程池状态
        if (runStateAtLeast(c, SHUTDOWN) && (runStateAtLeast(c, STOP) || firstTask != null || workQueue.isEmpty())){
            return false;
        }

        //CAS + 1
        for (;;) {
            //2.1 判断是否超过 corePoolSize OR maximumPoolSize
            if (workerCountOf(c)
                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            //2.2 线程状态改变,重新判断线程池状态,否则重新 CAS
            if (runStateAtLeast(c, SHUTDOWN))
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    //3 增加Worker线程/启动线程/执行第一个任务
    ...
        if (workerAdded) {
            t.start();
            workerStarted = true;
        }
    ...
    }
  • 申请工作线程并执行过程

回收Worker

线程池中,工作线程的销毁依赖 JVM 的垃圾回收机制。

getTask() 上面已经说过,核心线程会阻塞等待,超过核心线程数的部分会超时等待,如果这里返回了 null,则会跳出循环。调出后会在 processWorkerExit 中,将自己的引用从 workers 中移除掉。

final void runWorker(Worker w) {
  try {
    while (task != null || (task = getTask()) != null) {
      //执行任务
    }
  } finally {
    //获取不到任务时,主动回收自己
    processWorkerExit(w, completedAbruptly);
  }
}

Worker工作线程回收过程。

  • 将线程引用移出线程池就已经结束了线程销毁的部分。但由于引起线程销毁的可能性有很多,线程池还要判断是什么引发了这次销毁,是否要改变线程池的现阶段状态,是否要根据新状态,重新分配线程

 

使用Worker执行任务

当 addWorker 成功后,主线程就会拉起 worker 线程执行任务

final void runWorker(Worker w) {
    try {
        //1.0 循环获取任务(根据不同的 worker 类型,阻塞获取或超时阻塞)
        while (task != null || (task = getTask()) != null) {
            //1.1 申请到了任务,申请不可冲入锁
            w.lock();
            //1.2 如果线程池正在停止,但worker不是中断状态,则设置中断标志位
            if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()){
                wt.interrupt();
            }
            //1.3 执行任务
            task.run();
        }finally {
            //1.4 解锁,任务数 +1
            task = null;
            w.completedTasks++;
            w.unlock();
        }
    } finally {
        //2.0 获取不到任务时,主动回收自己
        processWorkerExit(w, completedAbruptly);
    }
}
  • 任务执行流程示意图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值