Java线程池的核⼼线程数和最⼤线程数Java的线程池就像是⼀个花瓶容器。
⽽把任务提交给线程池就像是把⼩球塞进花瓶。
整个过程就像下⾯这个有趣的动画:
下⾯我们先来了解⼀下Java线程池的参数。
希望看完这篇⽂章后, 再提起线程池的时候, 你脑海⾸先出现的, 会是⼀个花瓶 : )java线程池创建的四种
1 线程池的参数意义
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
别慌,我们可以把线程池的参数做成花瓶的参数,这样⼀来很多东西就不⾔⾃明了。
2 线程池的参数可视化
我们回到前⾯所说的花瓶。
这个花瓶由 瓶⼝ 、 瓶颈 、 瓶⾝ 三个部分组成。
这三个部分分别对应着线程池的三个参数: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不设计得很⼤, 就很容易导致溢出。