ThreadPoolExecutor的不确定性

网上介绍ThreadPoolExecutor的文章很多,这里不再累述。ThreadPoolExecutor是Doug lea的大作,但是只要是人写的代码都可能出现问题。

 

ThreadPoolExecutor是一个具有动态弹性的线程池,其会自动根据业务的请求量来动态伸缩其内的线程数目(当然判断的依据主要是corePoolSize、BlockingQueue的size与maximumPoolSize的大小关系来决策,对于线程池这样平台级的产物也只能按此标准了,想要按照业务自身特点的来控制线程的伸缩,需要继承ThreadPoolExecutor自己实现了),

 

简单说:

 

1、poolSize < corePoolSize 且线程池的状态为RUNNING时,产生新的worker线程处理任务。

 

2、poolSize = corePoolSize 时,将任务放入阻塞队列中,接着线程池会对volatile的runState进行double check,

 

进而判断刚刚加入队列的任务是否能继续呆在队列中等待处理(有可能刚加入队列,线程池就shutdown或者stop了)。

 

3、当队列满的时候,poolSize < maximumPoolSize, 线程池会增加线程来处理新的任务。最后当线程数达到

 

maximumPoolSize的时候,会根据拒绝策略来处理任务。

 

4、当队列中没有任务的时候,且线程池中存在空闲的线程(根据构建线程池传入的keepAliveTime作为到

 

BlockingQueue获取任务的超时时间),这样线程池会收缩空闲线程到corePoolSize。

 

这是ThreadPoolExecutor工作的基本原理。我们知道线程创建有一定的开销,这样我们才使用池化的构件思想来处理这

 

种昂贵的对象资源,所以我们应该更加关注对已有线程的释放,如果释放不当,可能导致在需要的时候又会重复重新创建

 

已经创建过的线程,形成不必要的开销。另外,如果线程池的伸缩策略不稳定,必然会影响业务的稳定性。那么,

 

ThreadPoolExecutor真的能把空闲的线程降低到corePoolSize吗?答案是半对半错的,在某些情况下可以,在某些情况

 

下会低于该值,乃至于降低到零。问题的根源在于ThreadPoolExecutor的getTask方法的实现:

 

   

Runnable getTask() {
        for (;;) {
            try {
                int state = runState;
                if (state > SHUTDOWN)
                    return null;
                Runnable r;
                if (state == SHUTDOWN)  // Help drain queue
                    r = workQueue.poll();
                else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
                    r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
                else
                    r = workQueue.take();
                if (r != null)
                    return r;
                if (workerCanExit()) {
                    if (runState >= SHUTDOWN) // Wake up others
                        interruptIdleWorkers();
                    return null;
                }
                // Else retry
            } catch (InterruptedException ie) {
                // On interruption, re-check runState
            }
        }
    }

   

 

    第10行代码在某些场景下是有缺陷的,这里不讨论降低到corePoolSize的正常情况,我们探讨下另外的场景。

 

    假设线程池的corePoolSize设置为3,阻塞对列为长度为3,maximumPoolSize的值为4,keepAliveTime为1秒,

 

线程池一共接收了7个任务,线程池的快照如下:

 



 

 

假设当R1、R2、R3被处理完后(R7仍然在处理中),那么corePoolSize线程去队列中获取R4、R5、R6任务进行处理,如下:

 

 

 

 这样当R4、R5、R6、R7任务都被处理完后,所有的线程(这里为4)都会调用getTask方法到队列中获取新的任务,如

 

下:

 



 

 

 

这样,当所有的线程都执行到

 

  else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
                    r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);

 

 

的时候,poolSize > corePoolSize 都为true(因为poolSize与corePoolSize都是volatile的,每个线程都能获取主存

 

的最新值),那么当在队列中等待keepAliveTime后(这里为1秒),所有的线程都将从队列中得到一个null值的返回,这样在

 

后面的逻辑中,都将以null值作为getTask的返回。这样每个worker线程都会退出自己的main loop,进而退出worker

 

的run方法(每个worker自身也是一个Runnable),最终线程池中的线程都降到零,而不是corePoolSize=3。

 

同时,使用ThreadPoolExecutor还要注意权衡corePoolSize、BlockingQueue队列的大小以及maximumPoolSize

 

三者的关系,否则使用ThreadPoolExecutor不当会给你的应用带来意想不到的负面结果。

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值