引言
前两天去面试的时候,面试官问了一个关于线程池的问题,问题大概的意思是:
当自定义线程池的时候,设置的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就已经堆内存溢出了😂)
其实这个问题之所以回答错误,还是我对队列不太是特别熟悉。前方的路还很远,需要学习的东西还很多啊!