线程池基本使用及原理分析
线程池基本使用及原理分析
从源码入手,分析线程池的基本使用场景以及核心代码原理分析。分析版本:jdk1.8
@
线程池介绍
In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model,[1] a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks.[2] The number of available threads is tuned to the computing resources available to the program, such as parallel processors, cores, memory, and network sockets.[3]
-- 摘要自维基百科
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
-- 摘要自百度百科
介绍一般都能理解,池这个概念,在平常的开发中也非常常见,例如数据库的连接池这些。主要还是为了方便管理和控制开销。
基本使用
@Test
public void contextLoads() throws IOException {
//创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//执行线程
executorService.execute(() -> {
System.out.println("running thread " + Thread.currentThread());
});
//关闭线程池
executorService.shutdown();
System.in.read();
}
上面的代码,就是一个简单的线程池使用方法。Executors是JUC提供的一个简单使用线程池的工具类,为不是很了解线程池的开发者提供了一层封装。
上面的代码是创建了一个FixedThreadPool。在Executors中还提供了一些其他的线程池的创建方法。
FixedThreadPool
创建一个固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
CachedThreadPool
这个线程池核心线程数为0,在执行线程时会优先判断是否有可用线程,如果有则使用,如果没有则创建。60秒后,线程被释放(下面的案例中)。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
SingleThreadExecutor
创建一个只包含单个线程的线程池。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
ScheduledThreadPoolExecutor
支持延迟执行的线程池。
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
除了以上四种外,Executors中还提供了一些其他的线程池有需要可以简单了解一下。
原理分析
线程池创建
从上面的几种线程看到,最终核心部分还是在于线程池的创建。下面是线程池创建的构造方法:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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;
}
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:非核心线程的存活时间
unit:时间的单位
workQueue:这个是任务的队列
threadFactory:线程工厂类
handler:执行拒绝策略时的回调,类似熔断的补偿
上面的部分不同类型的线程池实际就是通过调整这些参数实现的。
线程执行
流程图
execute
下面是线程池执行调度线程的方法:
/**
* 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();
int c = ctl.get();//这里的ctl是用来保存线程池的运行状态和线程数量,高低位运算的机制,高3位用来存状态,低29位用来保存线程数量
if (workerCountOf(c) < corePoolSize) {//判断当前运行的线程数是否小于核心线程数
if (addWorker(command, true))//添加工作线程,true false表示是否核心线程
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);
}
上面就是执行的大致流程,其实都是一些判断。具体的执行步骤实际在源码中作者已经有了标注:
/* * 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. */
而执行的核心代码,在于addWorker,接下来分析一下addWorker的核心代码。
addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {//其实这段代码就是检查一下状态和队列情况,然后cas工作线程数
int c = ctl.get();
int rs = runStateOf(c);
//检查线程池状态
//如果线程池已经shutdown,不会接受新的任务,但是未执行完的任务还是会继续执行
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))//cas工作线程数,成功跳出循环
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)//如果状态发生变化,continue
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;//启动标识
boolean workerAdded = false;//添加成功标识
Worker w = null;
try {
w = new Worker(firstTask);//这里的worker实现了Runnable
final Thread t = w.thread;//执行worker的线程
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
//检查线程池状态、检查firstTask状态、检查worker线程状态
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);//将worker添加到集合
int s = workers.size();
if (s > largestPoolSize)//如果集合长度大于最大线程数,更新记录最大线程数
largestPoolSize = s;
workerAdded = true;//标识添加成功标识
}
} finally {
mainLock.unlock();
}
if (workerAdded) {//添加成功后,启动worker中的线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)//如果失败了,会去执行回滚
addWorkerFailed(w);//执行回滚
}
return workerStarted;
}
addWorker的方法就是做一些判断校验,以及将firstTask添加到工作线程worker中。实际将逻辑委派给worker来执行,所以还需要了解一下worker的执行逻辑。worker的run方法中是调用runWorker(),因此看一下runWorker的方法逻辑。
runWorker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;//拿到初始化的firstTask
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//这个线程就是从task中取任务,如果任务不为空就一直循环执行任务
while (task != null || (task = getTask()) != null) {//这里的getTask是从阻塞队列中取任务
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
//依然是对一些状态的判断
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//这个是模板方法,默认无定义
beforeExecute(wt, task);
Throwable thrown = null;
try {
//直接调用task的run方法,而不是通过线程启动的
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//依然模板
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;//记录完成任务的数量
w.unlock();
}
}
completedAbruptly = false;
} finally {
//销毁worker
processWorkerExit(w, completedAbruptly);
}
}
分析完runWorker方法后,可以得出,task任务的执行实际就是通过直接调用task的run方法来完成的,而在执行完一个任务后,会循环从队列中取任务,在任务都执行完成后,去销毁worker。这块逻辑的关注点主要在getTask()方法以及processWorkerExit()方法,也就是从队列中获取任务以及任务执行完成后的销毁。接下来逐步分析一下getTask和processWorkerExit。
getTask
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//判断线程池如果shutdown了并且为stop或者队列中没有任务了,则减去workerCount并返回null(返回null后线程会退出)。
//可以看到,如果队列中还有任务,这里还是会执行完的(除非线程池STOP了)
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//拿到工作线程数
int wc = workerCountOf(c);
//这里的timed用来做超时判断
//allowCoreThreadTimeOut默认为false,表示核心线程默认一直保持活动
//如果工作线程数已经大于核心线程数了,也就是非核心线程
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果工作线程数已经大于最大线程数了或者校验超时控制(也就是任务超时了)
//并且任务队列已经没有任务了,则返回null
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//如果需要超时控制,通过poll取任务,否则通过take
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)//取到任务返回
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
分析完getTask方法可以发现,线程池如果shutdown了,任务还是会被执行完,但是如果是stop了,则不会继续执行了。核心线程通过take阻塞一直保持(除非设置了核心线程超时),非核心线程通过poll取任务,超时后会进入销毁逻辑。最后,在来看下销毁。
processWorkerExit
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//记录完成数量
completedTaskCount += w.completedTasks;
workers.remove(w);//集合中删除worker
} finally {
mainLock.unlock();
}
//尝试终止
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {//判断是否已经终止
if (!completedAbruptly) {//为false表示任务已经执行完了的场景
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;//这个min用来判断核心线程的数量
if (min == 0 && ! workQueue.isEmpty())//如果核心线程keep数量为0并且任务队列不为空则会至少保持一个worker的存活
min = 1;
if (workerCountOf(c) >= min)//如果当前的worker已经大于配置的核心线程数量了,则让线程被销毁
return; // replacement not needed
}
addWorker(null, false);
}
}
总结
通过线程池来执行任务,原理是通过委派给一个Worker对象来完成。
Worker对象本身也实现了Runnable接口,通过一个线程启动调用到Worker对象的run方法,Worker对象在来调用任务的run方法完成对象任务的逻辑执行。Worker对象同时继承了AQS,这是为了自定义实现独占锁的功能,这是为了表示当前线程正在执行任务中,不应该被中断。
核心线程会在getTask的时候通过调用阻塞队列的take方法阻塞,直到有新的任务加入队列重新开始执行。非核心线程在执行完任务后会被销毁,超时时间的表现是在从阻塞队列中取任务时,是调用take还是poll来完成。
核心线程会被回收的场景是在设置了allowCoreThreadTimeOut参数为true后,会将超时的核心线程销毁。