Java线程池—自定义线程池,当使用无界队列的时候,线程池数量达到corePoolSize怎么处理

2,413 阅读4分钟

引言

前两天去面试的时候,面试官问了一个关于线程池的问题,问题大概的意思是:

当自定义线程池的时候,设置的corePoolSize数量小于maximumPoolSize,并且使用的是无界队列(这里的无界队列指的是LinkedBlockingQueue)的时候,当线程池线程数量达到corePoolSize,这个时候继续往线程池里提交任务,线程池会怎么处理

我当时回答的是会继续创建线程,直到线程数量达到maximumPoolSize的时候,才会将处理不过来的任务放到队列里稍后处理。

验证

想要知道我回答的对不对很简单,写一段简单的代码就可以验证了

public class ThreadPoolDemo {

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5,
                1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
        for (int i = 1; i <= 5; i++){
            threadPoolExecutor.execute(new Task(i, threadPoolExecutor));
            Thread.sleep(10);
        }
    }

    static class Task implements Runnable{
        private int i;

        private ThreadPoolExecutor threadPoolExecutor;

        public Task(int i, ThreadPoolExecutor threadPoolExecutor) {
            this.i = i;
            this.threadPoolExecutor = threadPoolExecutor;
        }

        @Override
        public void run() {
            System.out.println("线程:"+i+" 运行, poolSize="+threadPoolExecutor.getPoolSize());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

我们看到上面代码,corePoolSize设置的是2,maximumPoolSize设置的是5,队列使用了一个LinkedBlockingQueue,它是一个无界队列(有关线程池的使用相关知识,请自行百度,我这里就不细讲了),如果按照我的回答会输出:

线程:1 运行, poolSize=1
线程:2 运行, poolSize=2
线程:3 运行, poolSize=3
线程:4 运行, poolSize=4
线程:5 运行, poolSize=5

但事实是运行上面代码输出的是:

线程:1 运行, poolSize=1
线程:2 运行, poolSize=2
线程:3 运行, poolSize=2
线程:4 运行, poolSize=2
线程:5 运行, poolSize=2

分析

显而易见,我的回答是错的,那么为什么错了呢?下面我们仔细分析一下

我们先来看一下ThreadPoolExecutor类的execute()方法

1.public void execute(Runnable command) {
2.        if (command == null)
3.            throw new NullPointerException();
4.        int c = ctl.get();
5.        if (workerCountOf(c) < corePoolSize) {
6.            if (addWorker(command, true))
7.               return;
8.            c = ctl.get();
9.        }
10.        if (isRunning(c) && workQueue.offer(command)) {
11.            int recheck = ctl.get();
12.            if (! isRunning(recheck) && remove(command))
13.                reject(command);
14.            else if (workerCountOf(recheck) == 0)
15.                addWorker(null, false);
16.        }
17.        else if (!addWorker(command, false))
18.            reject(command);
19.    }

我们先看第五行,workerCountOf(c)返回的是当前线程池线程的数量(其实这个描述并不准确,但是这里把它当成是线程池线程数量理解没有什么影响),当线程数量小于corePoolSize的时候,调用addWorker()方法新增线程(addWorker()方法也不展开讲了,这里只要知道这个方法是往线程池里面新增线程就行了)。

如果当前线程数量大于或等于corePoolSize的时候则执行第10行的代码,isRunning()方法是判断线程池是否正常运行,如果运行则执行workQueue.offer()方法将任务放入到队列,当方法返回true的时候继续执行if里面的代码,如果返回false则执行else if里面addWorker()方法添加非core线程。所以这里就知道我的回答为什么是错的了,因为当线程数量达到corePoolSize的时候,是直接将新任务放入队列,并不会创建新的线程。

看到这儿你可能又有疑问了,LinkedBlockingQueue是一个无界队列,是不是意味着offer()方法一直返回true,那岂不是可以一直往里添加任务,线程池的线程数量永远也达不到maximunPoolSize。真的是这样吗?

想要弄明白这个问题,我们就得看一下LinkedBlockingQueue的源码了,看一下它的offer()方法

1.public boolean offer(E e) {
2.        if (e == null) throw new NullPointerException();
3.        final AtomicInteger count = this.count;
4.        if (count.get() == capacity)
5.            return false;
6.        int c = -1;
7.        Node<E> node = new Node<E>(e);
8.        final ReentrantLock putLock = this.putLock;
9.        putLock.lock();
10.        try {
11.            if (count.get() < capacity) {
12.               enqueue(node);
13.                c = count.getAndIncrement();
14.                if (c + 1 < capacity)
15.                    notFull.signal();
16.            }
17.        } finally {
18.            putLock.unlock();
19.        }
20.        if (c == 0)
21.            signalNotEmpty();
22.        return c >= 0;
23.    }

我们能看到第4行,当队列的节点数量达到capacity的时候,直接返回false。what?不是说LinkedBlockingQueue是基于链表实现的,没有大小的吗,那这个capacity是哪儿来的,简直毁三观。 其实LinkedBlockingQueue也是有容量的,我们来看一下它的两个构造函数:

public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }


public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

我们看到LinkedBlockingQueue是可以指定容量的,如果没指定则默认为int的最大值。

结论

到这里一切都清楚了,再回到面试官的问题,正确的回答应该是:

无界队列其实也是有容量的,当线程池线程数量达到corePoolSize的时候,继续往线程池提交任务,会将任务放入队列,当队列满了的时候则会去创建非core线程处理任务(不过当使用LinkedBlockingQueue的时候,如果不指定队列容量,估计队列的任务还没达到最大值的时候,jvm就已经堆内存溢出了😂)

其实这个问题之所以回答错误,还是我对队列不太是特别熟悉。前方的路还很远,需要学习的东西还很多啊!