前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java并发——一文吃透线程池

Java并发——一文吃透线程池

原创
作者头像
淇妙小屋
发布2022-04-03 20:23:46
3240
发布2022-04-03 20:23:46
举报
文章被收录于专栏:面试题详解面试题详解

大家好,这里是淇妙小屋,一个分享技术,分享生活的博主,后续会发布更多MySQL,Redis,并发,JVM,分布式等面试热点知识,以及Java学习路线,面试重点,职业规划,面经等相关博客 转载请标明出处!

1. Executor框架介绍

1.1 任务的两级调度模型

应用程序通过Executor框架控制上层的调度

下层的调度有OS内核控制,不受应用程序控制

1.2 Executor架构结构

Executor架构分为三个部分

  • 任务
  • 任务的返回结果
  • 执行任务

2. Future接口

用于控制任务的执行,获得异步任务的执行状态,执行结果

向线程池提交Callable任务,线程池会返回一个Future对象供我们查看异步任务的执行状态,执行结果

代码语言:javascript
复制
Future<String>future=executor.submit(new Callable<String>(){});
//上下等价
​
RunnableFuture<String>future=new FutureTask<String>(异步任务);
executor.execute(future);

3. ThreadPoolExecutor详解

3.1 线程池的状态

  • 线程池有五种运行状态
    • RUNNING(运行)——可以接收新的任务并执行
    • SHUTDOWN(关闭)——不再接收新的任务,但是仍会处理已经提交的任务(包括线程正在执行的和处于阻塞队列中的)
    • STOP(停止)——不再接收新的任务,不会处理阻塞队列中的额任务,并且会中断正在执行的任务
    • TIDYING(整理)——所有的任务都已经终止,将线程池状态转换为TIDYING的线程会调用terminated()
    • TERMINATED(终止)——已经执行完毕terminated(),线程池终止
  • 线程池维护了一个AtomicInteger变量来表示线程池所处的状态(该变量还可以表示线程池中的线程数)
  • 线程池状态变化

3.2 手动创建线程池

代码语言:javascript
复制
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        //略
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数介绍

  • corePoolSize:初始化ThreadPoolExecutor中的corePoolSize,指定核心线程的数量
  • maximumPoolSize:初始化ThreadPoolExecutor中的maximunPoolSize,代表线程池中允许的最大线程数
  • keepAliveTime:初始化ThreadPoolExecutor中的keepAliveTime,空闲线程允许存活的最大时间
  • unit:keepAliveTime的单位
  • workQueue:初始化ThreadPoolExecutor中的workQueue,任务队列
  • threadFactory:初始化ThreadPoolExecutor中的threadFactory,线程工厂,用于创建线程
  • handler:初始化ThreadPoolExecutor中的handler,拒绝策略 ThreadPoolExecutor内置4种拒绝策略
    • AbortPolicy:直接抛出异常。
    • CallerRunsPolicy:由提交任务的线程处理任务
    • DiscardOldestPolicy:丢弃队列中最老的任务,重新提交这个被拒绝的任务
    • DiscardPolicy:不处理,丢弃掉。

3.3 execute()执行过程

过程](../p/execute()过程.png)

3.4 Woker和addWork()逻辑

  • 线程池中,线程被封装为Worker
  • thread:由线程池中的threadFactory创建
  • firstTask:创建Worker的时,可以指定firstTask,如果firstTask不为null,那么线程优先执行firstTask
  • 创建核心线程失败情况
    • 线程池的状态是STOP,TIDYING,TERMINATED
    • 线程池的状态是SHUTDOWN,并且传入了任务不是null(SHUTDOWN状态下,线程池不再接受新的任务)
    • 当前线程数>=允许的核心线程数
  • 创建非核心线程失败情况
    • 线程池的状态是STOP,TIDYING,TERMINATED
    • 线程池的状态是SHUTDOWN,并且传入了任务不是null(SHUTDOWN状态下,线程池不再接受新的任务)
    • 当前线程数>=允许的最大线程数

3.5 Worker工作过程

  • Woker执行的任务有2个来源
    1. Woker创建时指定的firstTask
    2. 从阻塞队列获取
  • Woker分为阻塞 核心线程非核心线程——根据 当前线程数目是否<= corePoolSize来判断(所以对于同一个线程,某一时刻可以是核心线程,另一时刻可以是非核心线程)
  • 默认情况下,核心线程会在阻塞队列永久阻塞获取,并且不会销毁,非核心线程只会在阻塞队列阻塞获取keepAliveTime的时间,超过了会进行销毁 但是如果线程池设置了 allowCoreThreadTimeOut,那么核心线程的待遇就跟非核心线程一样了

3.6 关闭线程池

3.6.0 尝试销毁线程池——tryTerminate()

尝试将线程池的状态更改为 TERMINATED,只有以下2种情况才能成功

  • 线程池状态为 SHUTDOWN,线程池中没有线程,并且阻塞队列为空
  • 线程池为 STOP,线程池中没有线程

3.6.1 缓慢关闭线程池——shutdown()

  1. 确保调用者有权限访问线程池中的线程
  2. 将线程池的状态修改为 SHUTDOWN
  3. 对线程池中的所有线程调用其interrupt()传递中断信号
  4. 调用tryTerminal()尝试销毁线程池
代码语言:javascript
复制
第4步大部分情况都不会成功
线程池状态变为SHUTDOWN后,线程池不会再接受新的任务,但已经接受的任务仍会继续执行,当所有任务执行完后,线程检测到线程池状态为SHUTDOWN并且任务队列空了,那么线程会执行退出操作——在退出操作中,每个线程都会执行一次 tryTerminal(),最后一个退出的线程可以成功销毁线程池

3.6.2 快速关闭线程池——shutdownNow()

  1. 确保调用者有权限访问线程池中的线程
  2. 将线程池的状态修改为 STOP
  3. 对线程池中的所有线程调用其interrupt()传递中断信号
  4. 移出任务队列中所有未执行的任务
  5. 调用tryTerminal()尝试销毁线程池
代码语言:javascript
复制
shutdownNow()移除任务队列中所有未执行的任务,从而实现快速关闭线程池

3.7 预热方法

3.7.1 prestartCoreThread

在线程池中预先创建一个线程

3.7.2 prestartAllCoreThreads

在线程池中创建所有的核心线程

3.8 面试题

3.8.1 如何理解keepAliveTime

如果线程池中的线程数目>corePoolSize,那么多余的线程一旦空闲超过keepAliveTime,就会销毁线程,直到线程数目==corePoolSize

3.8.2 为什么任务先放在任务队列中,而不是直接把线程数目拉到最大

我的个人理解

我认为线程池的本意是让核心数量的线程工作着,任务队列起到一个缓冲的作用,最大线程数目这个参数更像是无奈之举,在任务非常多的情况下做最后的努力,去新建线程来帮助处理任务

原生的线程池偏向于 CPU密集型,任务过多时不是创建更多的线程,而是先缓存任务,让核心线程去处理

而像Tomcat这种业务场景,是 IO密集型,原生的线程池并不合适,需要定制(Tomcat的线程池就是定制的)

4. ScheduledThreadPoolExecutor

  • 继承自ThreadPoolExecutor,用于在给定的延迟后执行任务or执行定时任务
  • 任务队列默认是用 DelayWorkQueue
  • 提交任务后,任务封装为ScheduledFutureTask后,直接进入任务队列,然后由线程从任务队列任务队列中获取ScheduledFutureTask执行

4.1 任务类图

4.2 任务提交

ScheduledThreadPoolExecutor中

schedule()scheduleAtFixedRate()scheduleWithFixedDelay()submit(),execute()的逻辑基本相同

接下来以schedule()为例进行讲解

ScheduledThreadPool中,任务封装为ScheduledFutureTask后,直接进入任务队列,然后由线程从任务队列中获取ScheduledFutureTask执行

4.3 任务的执行

  • DelayWorkQueue和ScheduledFutureTask的结构
  • 任务任务执行的步骤

5. FutureTask

  1. FutureTask可以由调用线程直接执行(FutureTask.run())(这种方式不会创建新的线程),也可以提交给线程池执行
  2. FutureTask跟Future一样,可以控制任务的执行状态,获得任务的执行结果

5.1 FutureTask状态迁移图

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Executor框架介绍
    • 1.1 任务的两级调度模型
      • 1.2 Executor架构结构
      • 2. Future接口
      • 3. ThreadPoolExecutor详解
        • 3.1 线程池的状态
          • 3.2 手动创建线程池
            • 3.3 execute()执行过程
              • 3.4 Woker和addWork()逻辑
                • 3.5 Worker工作过程
                  • 3.6 关闭线程池
                    • 3.6.0 尝试销毁线程池——tryTerminate()
                    • 3.6.1 缓慢关闭线程池——shutdown()
                    • 3.6.2 快速关闭线程池——shutdownNow()
                  • 3.7 预热方法
                    • 3.7.1 prestartCoreThread
                    • 3.7.2 prestartAllCoreThreads
                  • 3.8 面试题
                    • 3.8.1 如何理解keepAliveTime
                    • 3.8.2 为什么任务先放在任务队列中,而不是直接把线程数目拉到最大
                • 4. ScheduledThreadPoolExecutor
                  • 4.1 任务类图
                    • 4.2 任务提交
                      • 4.3 任务的执行
                      • 5. FutureTask
                        • 5.1 FutureTask状态迁移图
                        相关产品与服务
                        云数据库 MySQL
                        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档