一、请说出线程池的工作原理
答:常见流程如下:
- 提交任务后,先判断当前池中线程数是否小于 corePoolSize,如果小于,则创建新线程执行这个任务。
- 否则,判断线程池任务队列是否已满,如果没有满,则添加任务到任务队列。
- 否则,判断当前池中线程数是否大于 maximumPoolSize,如果大于则执行预设拒绝策略。
- 否则,创建一个线程执行该任务,直至线程数达到maximumPoolSize,达到后执行预设拒绝策略。
注意:其实按照上面流程回答已经差不多了,但是jdk1.6之后对流程做了一点点优化,在corePoolSize=0的时候任务不会长期阻塞。
jdk1.6之前:假设线程池当前corePoolSize=0时,则会判断线程池任务队列容量是否已满,若未满,则将任务添加进任务队列进行排队,并不会创建新的线程。
jdk1.6之后:假设线程池当前corePoolSize=0时,任务提交成功后会创建一个firstTask 为 null 的 worker,这个 worker 会从等待队列中获取任务并执行。
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
二、线程池创建之后,会立即创建核心线程么
答:查看线程池的构造器可以发现并不会立即创建核心线程,而是等到有任务提交时才会开始创建线程,除非调用了prestartCoreThread/prestartAllCoreThreads 事先启动核心线程。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
prestartCoreThread: 启动一个核心线程,使其空闲等待工作。这会覆盖仅在执行新任务时启动核心线程的默认策略。如果所有核心线程都已启动,此方法将返回 false。如果线程已启动,则返回 true。
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
}
prestartAllCoreThreads:启动所有核心线程,使它们空闲等待工作。这会覆盖仅在执行新任务时启动核心线程的默认策略。返回启动的线程数。
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
三、核心线程永远不会销毁么
答:其实核心线程只是一个动态概念,在jdk中并没有给线程打上"core"标记。而在jdk1.6之前,线程池会尽量保证会有corePoolSize个线程存活,即使这些线程已经闲置了很长的时间,这样会造成一部分资源浪费;于是在1.6开始,jdk提供了一个allowCoreThreadTimeOut方法用于控制核心线程是否被销毁:
/**
*设置控制核心线程是否可以超时并在保持活动时间内没有任务到达时终止的策略,
*如果需要,当新任务到达时被替换。当为 false 时,核心线程永远不会由于缺少传入任务而终止。
*如果为true,则适用于非核心线程的存活策略也适用于核心线程。
*为避免持续的线程替换,设置 {@code true} 时的 keep-alive 时间必须大于零。
*且通常应该在主动使用池之前调用此方法。
*
* @param value {@code true} if should time out, else {@code false}
* @throws IllegalArgumentException if value is {@code true}
* and the current keep-alive time is not greater than zero
*
* @since 1.6
*/
public void allowCoreThreadTimeOut(boolean value) {
if (value && keepAliveTime <= 0)
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
if (value != allowCoreThreadTimeOut) {
allowCoreThreadTimeOut = value;
if (value)
interruptIdleWorkers();
}
}
注意: 这种策略和corePoolSize=0是有区别的
- corePoolSize=0:在一般情况下只使用一个线程消费任务,只有当并发请求特别多、等待队列都满了之后,才开始用多线程。
- allowsCoreThreadTimeOut=true && corePoolSize>1:在一般情况下就开始使用多线程(corePoolSize 个),当并发请求特别多,等待队列都满了之后,继续加大线程数。但是当请求没有的时候,允许核心线程也终止。
综合来看,其实corePoolSize=0的效果基本等同于allowsCoreThreadTimeOut=true&&corePoolSize=1,只是实现细节不同。
四、线程池参数keepAliveTime=0 会怎么样
答:keepAliveTime这个参数1.6之前控制的是非核心线程的存活时间,且该参数值不能小于0,否则在创建线程池时会抛出异常。而设置为0的含义其实是指非核心线程执行完属于自己的任务后即刻销毁。从1.6开始,若 allowsCoreThreadTimeOut=true,则keepAliveTime必须大于0,否则也会报错。