一篇文章彻底弄懂java中的线程池,如何理解JAVA中的线程池


本文目的:通过图文的方式彻底弄懂线程池的工作原理

1. 什么是线程池

1、线程池其实是一种池化的技术的实现,池化技术的核心思想其实就是实现资源的一个复用,避免资源的重复创建和销毁带来的性能开销。

2、在线程池中,线程池可以管理一堆线程,让线程执行完任务之后不会进行销毁,而是继续去处理其它线程已经提交的任务。

3、线程池的好处:

  • 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高系统的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
2. 线程池的构造

Java 中主要是通过构建 ThreadPoolExecutor 来创建线程池的:

  • corePoolSize:线程池中用来工作的核心的线程数量。
  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
  • keepAliveTime:超出 corePoolSize 后创建的线程存活时间或者是所有线程最大存活时间,取决于配置。
  • unitkeepAliveTime 的时间单位。
  • workQueue:任务队列,是一个阻塞队列,当线程数已达到核心线程数,会将任务存储在阻塞队列中。
  • threadFactory :线程池内部创建线程所用的工厂。
  • handler:拒绝策略;当队列已满并且线程数量达到最大线程数量时,会调用该方法处理该任务。

线程池的构造其实很简单,就是传入一堆参数,然后进行简单的赋值操作。

3. 线程池的运行原理

上面这 7 个核心参数在线程池中是如何工作的。线程池刚创建出来的样子如下:

刚创建出来的线程池中只有一个在构建线程时传入的 阻塞队列 而已,此时里面并没有任何线程。

运行原理

1、当有线程通过 execute 方法提交了一个任务,会发生什么?----> 提交任务的时候,其实就会处理任务

首先判断 当前线程池的线程数 是否小于 核心线程数,如果小于,那么就直接通过 threadFactory 参数的值创建一个线程来执行这个任务,如图:

当任务执行完之后,线程不会退出,而是会去从阻塞队列中获取任务,如图:

接下来,如果又提交了一个任务,也会按照上述的步骤,去判断是否小于核心线程数,如果小,还是会创建线程去执行任务,执行完之后也会从阻塞队列中获取任务。这里有个细节:就是提交任务的时候,就算线程池里的线程从阻塞队列中获取不到任务,如果线程池里的线程数还是小于核心线程数,那么依然会继续创建线程,而不是复用已有的线程。

2、如果线程池里的线程数不再小于核心线程数呢?----> 那么此时就会尝试将任务放入阻塞队列中,如图:

这样在阻塞的线程就可以获取到任务了。

3、但是,随着任务越来越多,队列已经满了,任务放入失败,怎么办?----> 此时就会判断当前线程池里的线程数是否小于最大线程数,如果小于最大线程数,那么也会创建非核心线程来执行提交的任务,如图:

所以,从这里可以发现,就算队列中有任务,新创建的线程还是优先处理这个提交的任务,而不是从队列中获取已有的任务执行,从这可以看出,先提交的任务不一定先执行。

4、假如线程数已经达到最大线程数量,怎么办?----> 执行拒绝策略,来处理这个任务,如图:

JDK 自带的拒绝策略默认有 4 种:

  • AbortPolicy:丢弃任务,抛出运行时异常 (默认)
  • CallerRunsPolicy:由提交任务的线程来执行任务
  • DiscardPolicy:丢弃这个任务,但是不抛异常
  • DiscardOldestPolicy:从队列中剔除最先进入队列的任务,然后再次提交任务

可以自己实现 RejectedExecutionHandler 接口,比如将任务存到数据库或缓存中。

execute源码

知晓整个执行流程后,接下来看看 execute 方法是如何实现的,execute 方法源码如下:

  • workerCountOf(c)<corePoolSize:这行代码就是判断是否小于核心线程数,是的话就通过 addWorker 方法添加线程来执行任务。
  • workQueue.offer(command):这行代码就尝试往阻塞队列中添加任务
    • 添加失败之后就会再次调用 addWorker 方法尝试添加非核心线程来执行任务
      • 如果还是添加非核心线程失败了,那么就会调用 reject(command) 拒绝任务

总结:execute 执行流程如图:

4. 线程池中线程实现复用的原理

线程池的核心功能就是实现了线程的重复利用,那么线程池是如何实现线程的复用呢?----> 线程在线程池内部被封装成一个 Worker 对象

private final class Worker extends AbstractQueueSynchronizer implements Runnable{
   
   }

已经知道:创建线程来执行任务的方法 是通过 addWorker 方法创建的。在创建 Worker 对象的时候,会把线程和任务一起封装到 Worker 内部,然后调用 runWorker 方法来让线程执行任务。

// runWorker 方法源码如下:
final void runWorker(Worker w) {
   
   
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小学鸡!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值