Java ThreadPoolExecutor线程池使用的一个误区

新浪微博 QQ空间

Java对多线程的封装非常丰富,提供了多种适用于不同场景的多并发实现。其中最基础,最核心的线程池要属ThreadPoolExecutor类了。该类有如下几个构造函数:

 

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)

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

常用的几个参数:corePollsize,maximumPoolSize,workQueue。一般都知道,这三个参数决定了线程池的并发模型。但是具体怎么并发,却很容易混淆。

从字面意思很容易理解成,corePoolSize,是线程池的核心线程数,容易理解成线程池启动时就会创建这个数量的线程待用。而maximumPoolSize,很容易理解成,当提交的任务数大于核心线程数时,线程池会创建更多的线程来执行,直到线程池内的线程数达到maximumPoolSize值时就不会再创建新的线程了。如果还有新提交的任务,那么会入workQueue队列。当有线程空闲下来时,会到队列中取出任务来执行。当线程池队列为空时,并且当前线程池数目大于corePoolSize时,会释放部分线程,直到线程数等于corePoolSize为止。

事实真的如此吗?

我们仔细阅读这几个构造函数的详细说明会发现,上述理解是不正确的。线程池启动时,并不是一开始就创建corePoolSize数量的线程,而是根据提交任务的情况来的,当线程池内地线程数量少于corePoolSize时,提交任务时,会创建新的线程来执行,当线程数等于corePoolSize时,就会将任务放到队列中,如果队列长度不够大,那么,当任务队列满时,继续提交任务,则线程池会继续创建新的线程,此时线程池内地线程数大于corePoolSize。随着任务的提交,线程池内的线程数达到maximumPoolSize,则不再创建新的线程。再有任务提交时,如果队列满,但是线程池内的线程数已经达到最大值,那么提交任务会失败,抛出RejectedExecutionException异常。

上述解释看起来最终结果差异不大,对于参数的限定值最终效果类似,但是过程是完全不一样的,这会导致完全不同的并发执行结果。

针对线程池的正确用法中的解释,当线程池内的线程数达到corePoolSize时,创建新线程的条件并不是所有的corePoolSize个线程都在工作,没有空闲线程,才去创建新的线程,而是任务队列满,任务队列满与线程池内的线程都在工作之间没有必然联系,即,可能当前线程池内有空闲线程,但是由于队列不够大,很容易满,会导致不断创建新的线程,并且最终导致提交任务时收到rejected异常。

实测,的确存在该场景,因为workQueue是线程安全的同步阻塞队列,能支持的瞬时并发度有限,因此很容易让线程池中的队列积累一定数量的任务。这样,会导致实际的并发数没有达到maximumPoolSize时就会出现提交任务异常,而线程池中又有部分线程处于空闲状态。

因此,如果希望严格控制线程池的并发数量,应该尽量使用corePoolSize来控制,maximumPoolSize设置值与corePoolSize一致。如果使用者在不同的业务场景下,希望并发度不一样,在不需要更大规模的线程池时,则要自行调用ThreadPoolExecutor类的setCorePoolSize函数减少核心线程数,在并发执行任务需求大时,应该增大corePoolSize的数值。

setCorePoolSize
public void setCorePoolSize(int corePoolSize)

Sets the core number of threads. This overrides any value set in the constructor. If the new value is smaller than the current value, excess existing threads will be terminated when they next become idle. If larger, new threads will, if needed, be started to execute any queued tasks.

Parameters:
corePoolSize – the new core size
Throws:
IllegalArgumentException – if corePoolSize < 0
See Also:
getCorePoolSize()

来修改线程池内的线程数,并合理设置keepalivetime,让系统回收空闲线程。

另外线程池中的其他几个参数虽然使用不多,但也要注意含义:

ThreadFactory,线程创建工厂,只有一个接口,创建一个新的线程Thread实例,供线程池内需要创建新的线程时回调。一般再此定义线程名字等属性。

RejectedExecutionHandler,当出现rejected异常时,用于处理该异常的handler,线程池提供了三个默认实现,我们也可以自行继承该接口实现被拒绝任务的处理方式。

新浪微博 QQ空间

| 1 分2 分3 分4 分5 分 (5.00- 4票) Loading ... Loading ... | 这篇文章归档在:Java, 多线程编程, 语言基础 | 标签: , . | 永久链接:链接 | 评论(0) |

评论

邮箱地址不会被泄露, 标记为 * 的项目必填。

8 - 2 = *



You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <img alt="" src="" class=""> <pre class=""> <q cite=""> <s> <strike> <strong>

返回顶部