关于java:Java并发一文吃透线程池

8次阅读

共计 4145 个字符,预计需要花费 11 分钟才能阅读完成。

大家好,这里是 淇妙小屋 ,一个分享技术,分享生存的博主
以下是我的主页,各个主页同步更新优质博客,创作不易,还请大家点波关注
掘金主页
后续会公布更多 MySQL,Redis,并发,JVM,分布式等面试热点常识,以及 Java 学习路线,面试重点,职业规划,面经等相干博客
转载请表明出处!

1. Executor 框架介绍

1.1 工作的两级调度模型

应用程序通过 Executor 框架管制下层的调度

上层的调度有 OS 内核管制,不受利用程序控制

1.2 Executor 架构构造

Executor 架构分为 三个局部

  • 工作
  • 工作的返回后果
  • 执行工作

2. Future 接口

用于管制工作的执行,取得异步工作的执行状态,执行后果

向线程池提交 Callable 工作,线程池会返回一个 Future 对象供咱们查看异步工作的执行状态,执行后果

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 手动创立线程池

    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()执行过程

%E8%BF%87%E7%A8%8B.png)过程](../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() 尝试销毁线程池
第 4 步大部分状况都不会胜利
线程池状态变为 SHUTDOWN 后,线程池不会再承受新的工作,但曾经承受的工作仍会继续执行,当所有工作执行完后,线程检测到线程池状态为 SHUTDOWN 并且工作队列空了,那么线程会执行退出操作——在退出操作中,每个线程都会执行一次 tryTerminal(),最初一个退出的线程能够胜利销毁线程池

3.6.2 疾速敞开线程池——shutdownNow()

  1. 确保调用者有权限拜访线程池中的线程
  2. 将线程池的状态批改为 STOP
  3. 对线程池中的所有线程调用其 interrupt()传递中断信号
  4. 移出工作队列中所有未执行的工作
  5. 调用 tryTerminal() 尝试销毁线程池
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()为例进行解说

.png)

ScheduledThreadPool 中,工作封装为 ScheduledFutureTask 后,间接进入工作队列,而后由线程从工作队列中获取 ScheduledFutureTask 执行

4.3 工作的执行

  • DelayWorkQueue 和 ScheduledFutureTask 的构造

  • 工作工作执行的步骤

    1. 线程从 DelayWorkQueue 中获取超时的 ScheduledFutureTask

      (从 queue 中获取 queue[0],如果没有会阻塞期待,而后查看工作是否超时, 如果工作没超时,会阻塞直到工作超时)

    2. 线程执行工作
    3. 线程批改 ScheduledFutureTask 的 time 为下一次执行的工夫
    4. 线程将 ScheduledFutureTask 从新放回 DelayWorkQueue

5. FutureTask

  1. FutureTask 能够由调用线程间接执行(FutureTask.run())(这种形式不会创立新的线程),也能够提交给线程池执行
  2. FutureTask 跟 Future 一样,能够管制工作的执行状态,取得工作的执行后果

5.1 FutureTask 状态迁徙图

正文完
 0