vlambda博客
学习文章列表

线程池 keepAliveTime=0 时到底是什么含义???


点击蓝色小字关注!  关注一下你就不会有bug!

今天同事突然提出问题说用哪个线程池好,newFixedThreadPool 和 newCacheThreadPool 里选择,说固定大小线程池 keepAliveTime=0,线程空闲会立马回收线程从而节约资源,然后另外一个同事说,0 是代表永远不回收,我记忆里也是记得 0 是永久存活,因为网上很多博客啊,资料啊都是说的 0 表示线程永久存活在空闲的时候。前面那位同事也是从字面上认为的,没有经过验证,觉得 -1 才是永久不回收,然后各自进行了一波研究分析。

经过看源码,发现 keepAliveTime<0 是不行的,直接报错,也就是同事的猜测 -1 才是不回收 是错误的,看下面代码:

 1public ThreadPoolExecutor(int corePoolSize,
2                              int maximumPoolSize,
3                              long keepAliveTime,
4                              TimeUnit unit,
5                              BlockingQueue<Runnable> workQueue,
6                              ThreadFactory threadFactory,
7                              RejectedExecutionHandler handler)
 
{
8        if (corePoolSize < 0 ||
9            maximumPoolSize <= 0 ||
10            maximumPoolSize < corePoolSize ||
11            keepAliveTime < 0)
12            throw new IllegalArgumentException(); //异常了!!!!!!
13        if (workQueue == null || threadFactory == null || handler == null)
14            throw new NullPointerException();
15        this.acc = System.getSecurityManager() == null ?
16                null :
17                AccessController.getContext();
18        this.corePoolSize = corePoolSize;
19        this.maximumPoolSize = maximumPoolSize;
20        this.workQueue = workQueue;
21        this.keepAliveTime = unit.toNanos(keepAliveTime);
22        this.threadFactory = threadFactory;
23        this.handler = handler;
24    }

然后找 keepAliveTime 的使用一时半会也没找到,就用代码测试了,发现设置 keepAliveTime=0 核心线程数确实没有回收,后面同事说在并发病程的艺术那本书上有一段描述:

当线程池中的线程数大于 corePoolSize 时,keepAliveTime 为多余的空闲线程等待新任务的 最长时间,超过这个时间后多余的线程将被终止。这里把 keepAliveTime 设置为 0L,意味着多余 的空闲线程会被立即终止。

同事又开始迷茫了,我一开始没细看,觉得说的不对,后面反复阅读,发现这段文字描述的是说 keepAliveTime 控制的是非核心线程数的回收,也就是 0 的时候,非核心线程数会在空闲的时候回收,并不是说核心的会回收。为了验证结果,我们就用代码进行了测试,测试代码如下:

 1public class ExecutorsDemo implements Runnable{
2    private int i = 0;
3
4    public ExecutorsDemo(int i) {
5        this.i = i;
6    }
7
8    public static void main(String[] args) {
9        ThreadPoolExecutor executor = new ThreadPoolExecutor(1,2,0, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(1));
10        for (int i=0; i<3; i++) {
11            executor.execute(new ExecutorsDemo(i));
12        }
13        while(true) {
14            System.out.println("总线程数:"+executor.getPoolSize()+"当前活跃线程数:"+executor.getActiveCount());
15            try {
16                TimeUnit.SECONDS.sleep(1);
17            } catch (InterruptedException e) {
18                e.printStackTrace();
19            }
20        }
21    }
22
23    @Override
24    public void run() {
25        System.out.println("i="+i+" Thread = "+Thread.currentThread().getName());
26        if (i >= 1) {
27            try {
28                TimeUnit.SECONDS.sleep(1);
29                System.out.println("i="+i+" sleep 1 s结束");
30            } catch (InterruptedException e) {
31                e.printStackTrace();
32            }
33        } else {
34            try {
35                TimeUnit.SECONDS.sleep(3);
36                System.out.println("i="+i+" sleep 3 s结束");
37            } catch (InterruptedException e) {
38                e.printStackTrace();
39            }
40        }
41    }
42}

设置核心线程数和非核心线程数分别为 1 个,队列容量为 1,进入 3 个 runnable,第一个创建主线程,第二个进入队列,第三个则创建非主线程运行,输出结果为:

i=0 Thread = pool-1-thread-1

i=2 Thread = pool-1-thread-2

总线程数:2当前活跃线程数:2

总线程数:2当前活跃线程数:2

i=2 sleep 1 s结束

i=1 Thread = pool-1-thread-2

总线程数:2当前活跃线程数:2

总线程数:2当前活跃线程数:2

i=1 sleep 1 s结束

总线程数:1当前活跃线程数:1

总线程数:1当前活跃线程数:1

i=0 sleep 3 s结束

总线程数:1当前活跃线程数:0

可以看到非核心数线程执行完毕之后,队列中的 task 进入继续执行,等再次进入队列的task结束后,可以看到总线程数减少了1,而等核心线程执行完毕后,发现总线程数没有减少,但活跃线程数减少,也就是核心线程数没有回收。书上说的是正确的,大部分网上的博客说的keepAliveTime=0永久不回收是有出入的。

如果要设置核心线程的回收,则需要设置 executor.allowCoreThreadTimeOut(true),但这时 keepAliveTime 必须要 >0 才行,否则会抛出异常(throw new IllegalArgumentException("Core threads must have nonzero keep alive times");)。


往期精彩回顾


点击左下角阅读原文查看历史经典技术问题汇总,右下角素质三连呀