Java的线程池就像是一个花瓶容器。

而把任务提交给线程池就像是把小球塞进花瓶。

整个过程就像下面这个有趣的动画:

下面我们先来了解一下Java线程池的参数。

希望看完这篇文章后, 再提起线程池的时候, 你脑海首先出现的, 会是一个花瓶 : )


线程池的参数意义

Java线程池的构造函数如下:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {//...
}

线程池有这么几个重要的参数:

  • corePoolSize=> 线程池里的核心线程数量
  • maximumPoolSize=> 线程池里允许有的最大线程数量
  • keepAliveTime=> 空闲线程存活时间
  • unit=> keepAliveTime的时间单位,比如分钟,小时等
  • workQueue=> 缓冲队列
  • threadFactory=> 线程工厂用来创建新的线程放入线程池
  • handler=> 线程池拒绝任务的处理策略,比如抛出异常等策略

线程池大体的原理就是这样的:corePoolSize ->queue -> maxPoolSzie , 吧啦吧啦......

那么现在重点来了, 这堆参数解释不看源码真的搞不懂怎么办?

或者你看懂了这些参数的文字解析,但是到用的时候总是记不住怎么办?

或者我们来一组实际参数,你能理解这代表的含义吗?

corePoolSize:1
mamximumPoolSize:3
keepAliveTime:60s
workQueue:ArrayBlockingQueue,有界阻塞队列,队列大小是4
handler:默认的策略,抛出来一个ThreadPoolRejectException

别慌,我们可以把线程池的参数做成花瓶的参数,这样一来很多东西就不言自明了。

线程池的参数可视化

我们回到前面所说的花瓶。

这个花瓶由 瓶口 、 瓶颈 、 瓶身 三个部分组成。

这三个部分分别对应着线程池的三个参数:maximumPoolSize, workQueue,corePoolSize。

线程池里的线程,我用一个红色小球表示,每来一个任务,就会生成一个小球:

而核心线程,也就是正在处理中的任务,则用灰色的虚线小球表示 (目前第一版动画先这样简陋点吧......)

于是画风就变成了这样,“花瓶”有这么几个重要的参数:

  • corePoolSize=> 瓶身的容量
  • maximumPoolSize=> 瓶口的容量
  • keepAliveTime=> 红色小球的存活时间
  • unit=> keepAliveTime的时间单位,比如分钟,小时等
  • workQueue=> 瓶颈,不同类型的瓶颈容量不同
  • threadFactory=> 你投递小球进花瓶的小手 (线程工厂)
  • handler=> 线程池拒绝任务的处理策略,比如小球被排出瓶外

如果往这个花瓶里面放入很多小球时(线程池执行任务);

瓶身 (corePoolSize) 装不下了, 就会堆积到 瓶颈 (queue) 的位置;

瓶颈还是装不下, 就会堆积到 瓶口 (maximumPoolSize);

直到最后小球从瓶口溢出。

还记得上面提到的那一组实际参数吗,代表的花瓶大体上是如下图这样的:

那么参数可视化到底有什么实际意义呢?


3 阿里的规范

首先我们来看阿里开发手册中对于 Java 线程池的使用规范:

为什么规范中提及的四种线程会导致OOM呢?

我们看看这四种线程池的具体参数,然后再用花瓶动画演示一下导致OOM的原因。

线程池FixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());

我们关心的参数如下

corePoolSize:nThreads
mamximumPoolSize:nThreads
workQueue:LinkedBlockingQueue

FixedThreadPool表示的花瓶就是下图这样子:

线程池SingleThreadPool:

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
​  }

我们关心的参数如下

corePoolSize:1
mamximumPoolSize:1
workQueue:LinkedBlockingQueue

SingleThreadPool表示的花瓶就是下图这样子:


虽然两个线程池的样子没什么差异,但是这里我们发现了一个问题:

为什么 FixedThreadPool 和 SingleThreadPool 的 corePoolSize和mamximumPoolSize 要设计成一样的?

回答这个问题, 我们应该关注一下线程池的 workQueue 参数。

线程池FixedThreadPool和SingleThreadPool 都用到的阻塞队列 LinkedBlockingQueue。

LinkedBlockingQueue

The capacity, if unspecified, is equal to {@link Integer#MAX_VALUE}. Linked nodes are dynamically created upon each insertion unless this would bring the queue above capacity.

从LinkedBlockingQueue的源码注释中我们可以看到, 如果不指定队列的容量, 那么默认就是接近无限大的。

从动画可以看出, 花瓶的瓶颈是会无限变长的, 也就是说不管瓶口容量设计得多大, 都是没有作用的!

所以不管线程池FixedThreadPool和SingleThreadPool 的mamximumPoolSize 等于多少, 都是不生效的!


线程池CachedThreadPool

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());

我们关心的参数如下

corePoolSize:0
mamximumPoolSize:Integer.MAX_VALUE
workQueue:SynchronousQueue

表示的花瓶就是下图这样子:

这里我们由发现了一个问题:

为什么CachedThreadPool的mamximumPoolSize要设计成接近无限大的?

回答这个问题, 我们再看一下线程池CachedThreadPool的 workQueue 参数:SynchronousQueue。

SynchronousQueue

来看SynchronousQueue的源码注释:

A synchronous queue does not have any internal capacity, not even a capacity of one.

从注释中我们可以看到, 同步队列可以认为是容量为0。

所以如果mamximumPoolSize不设计得很大, 就很容易导致溢出。

但是瓶口设置得太大,堆积的小球太多,又会导致OOM(内存溢出)。

线程池ScheduledThreadPool

public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}

我们关心的参数如下

corePoolSize:corePoolSize
mamximumPoolSize:Integer.MAX_VALUE
workQueue:DelayedWorkQueue

可以看到, 这里出现了一个新的队列 workQueue:DelayedWorkQueue

DelayedWorkQueue

DelayedWorkQueue 是无界队列, 基于数组实现, 队列的长度可以扩容到 Integer.MAX_VALUE。

同时ScheduledThreadPool的 mamximumPoolSize 也是接近无限大的。

可以想象得到,ScheduledThreadPool就是史上最强花瓶, 极端情况下长度已经突破天际了!

到这里, 相信大家已经明白, 为什么这四种线程会导致OOM了。

怎么感觉这四种线程还真是名副其实的“花瓶”呢 :)


后续

目前花瓶动画还只是粗略的版本, 有部分瑕疵是不可避免的, 根据二八定律, 我的主要想法大体上是先做出来了,剩下的细节再慢慢补。

目前只体现了线程池的三个参数。

如果现在加入参数 keepAliveTime, 那么动画又会有什么效果的呢?

敬请期待后续更新的文章。


可视化的意义

有很多人或许会认为, 学习个线程池, 还要做什么动画, 这不是走偏了吗?

引用大神的一句话回答这个问题:

Data visualization knowledge is not necessary -- just the desire to spread some knowledge.

—— Ben Johnson

数据可视化确实不是必需的, 但是有时候我们仅仅只是渴望给大家分享一些知识。

而且在这个分享的过程中, 动画会让你做出更多的思考:

思考动画怎么才能符合真实场景的效果。

比如当我们开始思考,动画中花瓶颈部的长度变化,以及DelayedWorkQueue队列容量的变化,这两者如何才能对应的上时,于是不可避免的, 我们会开始研究起DelayedWorkQueue的扩容方式

甚至每一种队列都可以单独展开做成更加细化的动画。

而想要做好这些动画, 又要开始研究不同队列的源码了, 有需求才有动力!

/*** 简单配置示例*///获取当前机器的核数
public static final int cpuNum = Runtime.getRuntime().availableProcessors();@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(cpuNum);//核心线程大小taskExecutor.setMaxPoolSize(cpuNum * 2);//最大线程大小taskExecutor.setQueueCapacity(500);//队列最大容量//当提交的任务个数大于QueueCapacity,就需要设置该参数,但spring提供的都不太满足业务场景,可以自定义一个,也可以注意不要超过QueueCapacity即可taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());taskExecutor.setWaitForTasksToCompleteOnShutdown(true);taskExecutor.setAwaitTerminationSeconds(60);taskExecutor.setThreadNamePrefix("BCarLogo-Thread-");taskExecutor.initialize();return taskExecutor;}

Java线程池的核心线程数和最大线程数相关推荐

  1. 【Java面试小短文】当任务数超过线程池的核心线程数,如何让它不进入阻塞队列直接启用最大数量的线程去执行任务?

    欢迎关注Java面试系列,不定期更新面试小短文.欢迎一键三连! 当任务数超过线程池的核心线程数,如何让它不进入阻塞队列直接启用最大数量的线程去执行任务? 当我们提交一个任务到线程池,它的工作原理如下: ...

  2. Java Executor源码解析(3)—ThreadPoolExecutor线程池execute核心方法源码【一万字】

    基于JDK1.8详细介绍了ThreadPoolExecutor线程池的execute方法源码! 上一篇文章中,我们介绍了:Java Executor源码解析(2)-ThreadPoolExecutor ...

  3. Java 线程池框架核心代码分析--转

    原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...

  4. Java线程池框架核心代码分析

    前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和资源消耗都是很高的.线程池应运而生,成为我们管理线程的利器.Java 通过Executor接口,提供了一种标准的方法将任务的提交过 ...

  5. Java 线程池框架核心代码分析

    转载自 Java 线程池框架核心代码分析 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和资源消耗都是很高的.线程池应运而生,成为我们管理线程的利器.Java 通过Executo ...

  6. hibernate 并发获取session失败 空指针_高并发之|通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程...

    核心逻辑概述 ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态. ThreadPoolExecu ...

  7. 线程池的核心线程会销毁吗?

    今天跟别人讨论了线程池的核心线程会不会销毁的问题 先上代码 public static void main(String[] args) throws InterruptedException {Th ...

  8. Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理

    相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...

  9. 并发编程系列---【线程池七大核心参数】

    一.七大核心参数 1.corePoolSize                            核心线程数 2.maximumPoolSize                   最大线程池参数 ...

  10. 【Android 异步操作】线程池 ( Worker 简介 | 线程池中的工作流程 runWorker | 从线程池任务队列中获取任务 getTask )

    文章目录 一.线程池中的 Worker ( 工作者 ) 二.线程池中的工作流程 runWorker 三.线程池任务队列中获取任务 getTask 在博客 [Android 异步操作]线程池 ( 线程池 ...

最新文章

  1. (转)SSH批量分发管理非交互式expect
  2. 重磅!GitHub官方App官宣发布了
  3. linux如何运行多个硬盘,一个硬盘如何装两个Linux
  4. PHP 一个比较完善的文件上传(转).
  5. Redis-ha(sentinel)搭建
  6. open() 函数以 w+ 模式打开文件
  7. Confluence 6 理解你许可证的用户数
  8. Opencv环境配置
  9. VS2017的C++开发心得(一)VS的项目创建
  10. Wifite批量破解WEP/WPA/WPS
  11. (一)人工智能、AI批量抠图、AI视频抠像、图片换背景、视频换背景、实时抠图、实时抠像、虚拟场景直播系统、虚拟旅游、人像去背景、图像去背景、视频背景消除
  12. linux 系统安装微信小程序开发工具
  13. win10如何打来计算机的工具,电脑系统教程:Win10自带解压缩文件工具如何使用
  14. 全文六万字《计算智能》智能优化算法 张军【Python】
  15. 计算机函数两个表格找相同,wps筛选出两个表格中的重复项(countif 函数简单使用)【已解决】...
  16. python中减号怎么打_我的python将减号与破折号混淆
  17. backtrader_plotting报错Unexpected data type
  18. powerbuilder建数据库
  19. 深蓝卡通风人教版小学五年级英语课件PPT模板
  20. selenium安装及配置

热门文章

  1. 在 Linux 上使用 yuzu 模拟 Nintendo Switch 试玩王国之泪
  2. 顺利将2D内容导入Unity的良好工作流程,第二部分:导入
  3. python从word文档里提取标题并生成目录
  4. 人员定位系统的市场应用价值
  5. 易语言API hook 实现动态类名
  6. 基于imx8m plus开发板全体系开发教程3:Ubuntu 20.04 编译
  7. 基于springboot的海鲜特产商城
  8. 三分钟看Netty(一) 纵观全局
  9. FlyAI小课堂:Python机器学习笔记:Logistic Regression
  10. div地址跳转 vue_vue跳转页面的几种方法(推荐)