线程池
1、介绍
使用线程池主要解决两个问题:
- 当执行大量异步操作时线程池能够提供较好的性能。(不使用线程池时需要new一个线程来运行,而线程的创建和销毁都是需要耗费性能的。)线程池中的线程是可以复用的,不需要每次执行异步任务时都创建和销毁线程。
- 线程池提供了资源限制和管理手段,比如线程线程的个数,动态新增线程等。
另外线程池也提供了许多可调参数和可扩展性接口,以满足不同情景的需要,程序员可以使用Executors的工厂方法,比如
- newCachedThreadPool(线程池个数可以达到达 Integer.MAX_VALUE,线程自动回收)
- newFixedThreadPool(固定线程数目的线程池)
- newSingleThreadExecutor(单个线程的线程池)
2、类图
在以上的类图中可以看到ThreadPoolExecutors继承了AbstractExecutorService,成员变量ctl是一个Integer的原子变量,用来记录线程线程池状态和线程池中线程个数,类似于ReentrantReadWriteLock使用一个变量来保存两种信息。
下面通过源码来解释:
//(高3位表示线程池状态),(低29位表示线程个数)
//默认时RUNNING,线程个数位0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程个数掩码位数,并不是所有平台的int类型都是32位的,所以说是具体平台下Integer的二进制位数-3后剩余位数所表示的的数才是线程的个数
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程最大个数
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//线程池状态
// runState is stored in the high-order bits
// 11100000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
//00000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//00100000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
//01000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
//01100000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
//获取高3位的运行状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取低29位的线程个数
private static int workerCountOf(int c) { return c & CAPACITY; }
//计算ctl新值(线程状态与线程个数)
private static int ctlOf(int rs, int wc) { return rs | wc; }
线程状态含义:
RUNNING
:接受新任务并且处理阻塞队列里的任务SHUTDOWN
:拒绝新任务但是处理阻塞队列里的任务STOP
:拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务TIDYING
:所有任务都执行完(包括阻塞队列中的任务)后当前线程活动线程数位0,将要调用terminated方法TERMINATED
:终止状态,terminated方法调用完成以后的状态
线程状态转换含义:
- RUNNING->SHUTDOWN:显式调用shutdown方法,或者隐式调用finalize方法 中的shutdown方法
- RUNNING或SHUTDOWN->STOP:显式调用shutdownNow方法
- SHUTDOWN->TIDYING:当线程池和任务队列都为空时。
- STOP->TIDYING:当线程池为空时
- TIDYING->TERMNATED:当terminated方法执行完成时
线程池参数如下:
corePoolSize
:线程池核心线程个数workQueue
:保存等待执行的任务的阻塞队列,比如基于数组的有界ArrayBlockingQueue、基于链表的LinkedBlockingQueue、最多只有一个元素的同步队列SynchronousQueue、优先级队列PriorityBlockingQueue等。maximunPoolSize
:线程池最大线程数量ThreadFactory
:创建线程的工厂RejectedExecutionHandler
:拒绝策略,当队列满时并且线程个数达到maximunPoolSize后采取的策略,比如AbortPolicy(抛出异常)、CallerRunsPolicy(使用调用者所在线程运行任务)、DiscardOldestPolicy(调用poll丢弃一个任务,执行当前任务)、DiscardPolicy(默默丢弃,不抛出异常)。keeyAliveTime
:存活时间,如果当前线程池中的线程数量为核心线程数量,并且是闲置状态,则这些闲置的线程能存活的最大时间。TimeUnit
:存活时间的时间单位newFixedThreadPool
:固定线程数的线程池,并且阻塞队列的长度为Integer.MAX_VALUE。keeyAliveTime=0说明只要线程个数比核心线程个数多并且当前空闲则回收。newCachedThreadPool
(线程池个数可以达到达 Integer.MAX_VALUE,线程自动回收),线程初始个数为0,最多线程个数为Integer.MAX_VALUE,并且阻塞队列为同步队列。keeyAliveTime=60说明线程在60s内空闲则回收。这个类型的特殊在于加入同步队列的任务会被马上执行,同步队列里面最多只有一个任务newSingleThreadExecutor
(单个线程的线程池):这是一个核心线程个数以及最大线程个数都为1的线程池,并且阻塞队列长度为Integer.MAX_VALUE.
明天继续
3、源码分析
3.1 public void excute(Runnable command)
excute方法的作用的提交任务command到线程池中执行。用户线程提交任务到线程池的模型为
从该图中可以看到ThreadPoolExcutor的实现实际是一个生产消费模型,当用户添加任务到线程池时相当于生产者的生产元素,workers线程工作集中的线程直接执行任务或者从任务队列里面获取任务时则相当于消费者消费元素。
public void execute(Runnable command) {
//(1)如果任务为null,则抛出异常
if (command == null)
throw new NullPointerException();
//(2)获取当前线程池状态+线程个数变量组合值
int c = ctl.get();
//(3)当前线程池中的线程个数是否小于corepoolsize,小于则开启新线程运行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//(4)如果线程池处于RUNNING状态,则添加任务到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
//(4.1)二次校验
int recheck = ctl.get();
//(4.2)如果当前线程池状态不是RUNNING,则从任务队列中删除任务,并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//(4.3)否则如果当前线程池为空,则添加一个线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//(5)如果队列满,则新增线程,新增失败则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
代码(3)中如果当前线程池中线程个数小于corepoolsieze,则会向workers里面新增一个核心线程,然后执行该任务。
如果当前线程池中线程个数大于等于corepoolsize,则执行代码(4)。如果当前线程池状态处于RUNNING状态则添加当前任务到任务队列,这里需要判断线程池状态是因为如果线程池状态非RUNNING时,则需要抛弃新任务。
如果向任务队列中添加任务成功,则需要对线程池进行二次校验,这是因为添加任务到任务队列后,在执行(4.2)前线程池状态可能已经变化了。因此这里对线程池进行二次校验,判断如果线程池状态不是RUNNING状态则移除该任务。
如果添加任务失败,则说明任务队列已满,那么执行(5)尝试开启新线程来执行任务,如果当前线程池线程个数大于maximumPoolsize,则执行拒绝策略。
接下来查看新增任务队列方法addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//检查队列是否只在必要时位空
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//循环CAS增加线程个数
for (;;) {
int wc = workerCountOf(c);
//如果线程个数超过限制则返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//CAS增加线程个数,同时只有一个线程成功
if (compareAndIncrementWorkerCount(c))
break retry;
//如果CAS失败了,则查看线程池状态是否变化了,变化则跳到外层循环重新尝试获取线程池状态,否则内存循环重新CAS
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//这里说明已经CAS成功
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建Worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//加独占锁,为了实现worker同步,因为可能多个线程调用了线程池的excute方法
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
//重新检查线程状态,以避免在获取锁前调用了shutdown接口
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//添加任务
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//添加成功后则启动任务
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorker代码比较长,主要分为两部分,第一部分的目的是通过CAS增加线程数,第二部分是把并发安全的任务添加到worker里面。
当用户提交线程到线程池后,线程会由Wokrer来执行,Worker的构造函数中,首先将Worker的状态设置为-1,这是为了避免当前Worker在执行runWorker前被中断,这里设置状态为-1,线程就不会被中断了
Worker(Runnable firstTask) {
setState(-1); //在调用 runWorker前禁止中断
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);//创建一个线程
}
接下来再看runWorker方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts 将状态设置为0,允许被中断
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
//如果线程池停止,则当前线程会被中断,如果没有则当前线程不允许被中断
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();//执行任务
} 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;
//统计当前Worker完成了多少任务
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//执行清理工作
processWorkerExit(w, completedAbruptly);
}
}
3.2 public void shutdown()
当线程池执行shutdown操作后,线程池就不会再接受新的任务,但是工作队列中的任务还是需要执行的。该方法会立刻返回,并不等待队列任务完成再返回。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//权限检查
checkShutdownAccess();
//设置当前线程池状态为SHUTDOWN,如果已经是SHUTDOWN则直接返回
advanceRunState(SHUTDOWN);
//设置中断标志
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//尝试将状态变为TERMINATED
tryTerminate();
}
在该方法中首先检查是否设置了安全管理器,如果已经设置了则查看当前调用shutdown命令的线程是否有关闭线程的权限如果有权限则还要查看调用线程是否有中断工作线程的权限,如果没有权限则抛出SecurityException或者NullPointerException异常。
其中如果当前线程池状态>=SHUTDOWN则直接返回,否则设置SHUTDOWN状态。
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
4、总结
在线程池中使用了一个Integer 类型的原子变量来记录线程池状态和线程池中的线程个数。
通过线程池状态来控制任务的执行,每个Worker线程可以处理多个任务。
线程池通过线程的复用减少了线程创建和销毁的开销。