ThreadPoolExecutor 的简单梳理

代码星冰乐

专注成就未来

首页 归档 关于

ThreadPoolExecutor 的简单梳理

Jan 19, 2020 | haifeiWu | ThreadPoolExecutor | 阅读
文章目录
  1. 1. what ?
  2. 2. why ?
  3. 3. how ?
  4. 4. 小结

作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2020/c5a/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。


由于版权原因,请阅读原文 --> ThreadPoolExecutor 的简单梳理

关注我们

作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2020/c5a/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2020/c5a/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

还是楼主惯用的论述三连问,先问是什么,再问为什么,最后祭除终极大杀器 just do it ……

what ?

那么什么是线程池呢?总的来说,线程池是一种线程使用模式。线程的频繁创建于调度会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

why ?

上面说的是整个线程池的总体概念,当然 Java 中的线程池也是基于相同的理念设计的。在 Java 线程池可以提高线程复用,又可以固定最大线程使用量,防止无限制地创建线程。当程序提交一个任务需要一个线程时,会去线程池中查找是否有空闲的线程,若有,则直接使用线程池中的线程工作,若没有,会去判断当前已创建的线程数量是否超过最大线程数量,如未超过,则创建新线程,如已超过,则进行排队等待或者直接抛出异常。如下图所示

总的来说,线程池就是把资源池化,预先准备好,以避免不必要额外开销。

how ?

Java 提供了一套 Executor 框架,根据常用的场景对 ThreadPoolExecutor 类做了简单的封装,当然这样做的话难免有些束手束脚,所以大部分情况下都是根据自己的业务需求直接调用 ThreadPoolExecutor 实现自己的线程池。

这个框架中包括了 ScheduledThreadPoolExecutor 和 ThreadPoolExecutor 两个核心线程池。前者是用来定时执行任务,后者是用来执行被提交的任务。因为这两个线程池的原理是一样的,都是调用底层的 ThreadPoolExecutor,下面我们就重点看看 ThreadPoolExecutor 类是如何实现线程池的。

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
int maximumPoolSize,//线程池的最大线程数
long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler) //拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务

通过上面代码,我们发现线程池有两个线程数的设置,一个为核心线程数,一个为最大线程数。在创建完线程池之后,默认情况下,线程池中并没有任何线程,等到有任务来才创建线程去执行任务。

当创建的线程数等于 corePoolSize 时,提交的任务会被加入到设置的阻塞队列中。当队列满了,会创建线程执行任务,直到线程池中的数量等于 maximumPoolSize。当线程数量已经等于 maximumPoolSize 时, 新提交的任务无法加入到等待队列,也无法创建非核心线程直接执行,当我们又没有为线程池设置具体的拒绝策略时,线程池就会抛出 RejectedExecutionException 异常,即线程池拒绝接受当前提交的任务。

当线程池中创建的线程数量超过设置的 corePoolSize,在某些线程处理完任务后,如果等待 keepAliveTime 时间后仍然没有新的任务分配给它,那么这个线程将会被回收。线程池回收线程时,会对所谓的“核心线程”和“非核心线程”一视同仁,直到线程池中线程的数量等于设置的 corePoolSize 参数,回收过程才会停止。从下面代码中可以看出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0 || maximumPoolSize < corePoolSize)
throw new IllegalArgumentException();
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
else if (delta > 0) {
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}

小结

到目前为止,楼主只是围绕 ThreadPoolExecutor 的构造方法,简单阐述了下 Java 中的线程池实现的基本逻辑,想要更深入了理解,也可以尝试在阅读完 JDK 的线程池实现源码之后造个轮子。

关注我们

作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2020/c5a/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

分享
Java
【译】Raft 学生指南MapReduce 的简单实现
微信关注我们
分类
  • Android8
  • Go4
  • Java59
  • Kafka,Java1
  • Kotlin2
  • Linux1
  • MapReduce1
  • Python2
  • Raft1
  • Redis1
  • ThreadPoolExecutor1
  • go1
  • 工具1
  • 总结8
  • 旅游日记1
标签
Nginx ChanghuiN haifeiWu Android Java 设计模式 hexo Kotlin 算法 MySQL 源码解析 Python Redis golang web Kafka 配置中心 总结 性能优化 旅游日记 Shell Go 问题排查 译文 Docker Spring Boot 工具 学习笔记 WebFlux 性能测试 go 散列表 源码 netty Raft
最近文章
  • Kafka的日志复制机制
  • 从20到21
  • go 并发编程
  • 【译】了解Linux CPU负载-您何时应该担心?
  • Zookeeper 与分布式锁
  • 基于Redis的分布式锁到底安全吗?
  • 【译】Raft 学生指南
  • ThreadPoolExecutor 的简单梳理
  • MapReduce 的简单实现
  • 使用 Map 实现策略模式
福利专区
    免费SSL证书
      阿里云红包
        腾讯云专属福利
        Copyright © 2021 代码星冰乐. Powered by ChanghuiN. 版权所有 晋ICP备15001365号
        特别感谢: 云服务器服务商 、 CDN 服务商