多线程 Part 5 - 线程池
一、两种线程模型
1. 用户线程ULT
由用户程序实现,不依赖于操作系统。不需要用户态与核心态的切换,速度快。内核对用户线程无感知,线程阻塞则进程阻塞。
2. 内核线程KLT
由操作系统管理,线程阻塞不会引起进程阻塞。在多处理器下,多线程在多处理器上并行运行。效率比用户线程低。JVM基本用的是KLT。从Java创建的线程会1:1的对应到内核,然后由CPU调度执行。
二、为什么要有线程池
创建线程和销毁线程都是很耗资源的操作,而Java线程依赖内核线程,创建线程需要进行操作系统状态切换。为了避免过度消耗资源,有了线程池,作为线程缓存的区域。
三、线程池的好处
- 降低资源消耗
- 提高响应速度
- 方便管理
- 线程复用,可以控制最大并发数,管理线程
四、线程池使用
1. 三大使用
a). 单个线程
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程
try {
for (int i = 0; i < 5; i++) {
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} finally {
threadPool.shutdown();
}
}
}
输出:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
b). 创建一个固定大小的线程池,这里大小设为3,所以最多会有3个线程执行
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3); //创建一个固定大小的线程池
try {
for (int i = 0; i < 5; i++) {
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} finally {
threadPool.shutdown();
}
}
}
输出:
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-3
pool-1-thread-1
c). 能有多少就有多少,这里输出创建了8个线程
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} finally {
threadPool.shutdown();
}
}
}
输出:
pool-1-thread-1
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
pool-1-thread-6
pool-1-thread-7
pool-1-thread-5
pool-1-thread-8
pool-1-thread-2
pool-1-thread-8
2. 七大参数
无论是single, fixed, Cached,通通调用的都是 ThreadPoolExecutor 类来进行线程池创建
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, //内置参数太大,容易OOM
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
我们来看一下 ThreadPoolExecutor 类构造器,参数列表里就是传说中的七大参数
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;
}
在阿里巴巴开发手册里面规定了,不要用Executors创建线程池,而是要用ThreadPoolExecutor来创建,因为确实用Executors不安全,有OOM问题。
3. 线程池结构
那么我们先来举个例子看一下线程池的结构,再来讨论着七大参数分别有什么意义。
比如银行办理业务。这个银行有5个柜台,但是一般情况下不会5个都开放,可能只开放3个。银行里还会有等候区域,如果客户过来发现有空闲的柜台,那他就可以直接去办理业务;如果三个开放柜台都有人在用了,那他就要去等候区等待。
但是银行的等待区域也是有一定容量的,如果等待区域满,那就说明来的客户很多了,那就要把没有开放的柜台也开放进行业务受理。但如果这个时候,还是有源源不断的客户进来,所有柜台都在受理,等待区域也占满了,那么这个时候外面来的客户可能就会被告知等会儿再来吧。之后过了高峰期,人流又会减少,那么右边那两个柜台发现很久没人来受理业务了,那它们就会关闭。
那么这个银行的场景就可以类比到线程池,并且对应我们的七大参数:
int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂,创建线程用的
RejectedExecutionHandler handler) { //拒绝策略
那么对于这个银行,corePoolSize = 3, maximumPoolSize = 5
4. 四种拒绝策略
对于拒绝策略RejectedExecutionHandler,有四种实现:
默认的拒绝策略是AbortPolicy
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
我们一个个来研究一下都是什么策略:
- AbortPolicy:直接拒绝,可以看到
rejectedExecution
方法里面直接抛出了一个异常
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
- CallerRunsPolicy:让线程执行它原有的业务,可谓从哪里来回哪里去。线程池不会受理它的业务。如果线程池正好这个时候被关闭了,那进来的这个线程就回不到原来的地方了。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
- DiscardPolicy:可以看到
rejectedExecution
里什么也没做,所以进来的线程也是直接被拒绝,只不过不会报异常
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
- DiscardOldestPolicy:这个和前三个有很大的不同。如果线程池关闭了,那当然什么也不会发生。如果线程池开着,那就把阻塞队列里面第一个排队的给移走,然后尝试让线程池受理新进来的这个线程。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
5. 怎么确定最大核心线程数
- CPU密集型:把 maximumPoolSize 就设置为CPU核数
- IO密集型:判断程序中十分占IO的任务有多少个,然后设置 maximumPoolSize 至少大于这个个数
//打印本机CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
五、线程池的五种状态
//COUNT_BITS是29
private static final int RUNNING = -1 << COUNT_BITS; //-1十六进制是ffffffff
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
- Running:能接受新任务以及处理已添加的任务
- Shutdown:不接受新任务,但可以处理已经添加的任务
- Stop:不接受新任务,不处理已经添加的任务,并且中断正在处理的任务
- Tidying:所有的任务已经终止
- Terminated:线程池彻底终止
线程池状态转换
我们把这五种状态的数值整理一下:
状态 | ||||
---|---|---|---|---|
RUNNING | 1110 0000 | 0000 0000 | 0000 0000 | 0000 0000 |
SHUTDOWN | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 |
STOP | 0010 0000 | 0000 0000 | 0000 0000 | 0000 0000 |
TIDYING | 0100 0000 | 0000 0000 | 0000 0000 | 0000 0000 |
TERMINATED | 0110 0000 | 0000 0000 | 0000 0000 | 0000 0000 |
我们发现五种状态被存在一个32位的Integer里。这里高3位代表状态,低29位代表当前工作的线程数
六、线程池工作原理
execute()
- 如果当前工作线程小于核心线程数,那就创建一个核心线程worker并且负责执行传入的command任务
- 如果当前工作线程大于核心线程数,那就把传入的command任务放入阻塞队列里
- 如果阻塞队列满了,传入的command无法放入,那就尝试增加非核心线程worker
- 如果非核心线程数也满了,那就使用拒绝策略,拒绝传入的command任务
- 以上情况都是建立在线程池running状态,如果线程池准备终止了,那传入的command不会被受理,也不会有新的worker被创建
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get(); //一开始拿到的是RUNNING状态 0xe0000000
if (workerCountOf(c) < corePoolSize) { //第一次拿到的当前工作线程数是0,小于核心线程数
if (addWorker(command, true)) //尝试增加一个线程去执行command的事务,如果返回false则增加失败
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //判断当前状态是不是running状态,并且将command加入阻塞队列中
int recheck = ctl.get(); //重新获取线程池状态
if (! isRunning(recheck) && remove(command)) //如果线程池不是running状态,那就将command从阻塞队列移出
reject(command); //并且用相应拒绝策略拒绝command
else if (workerCountOf(recheck) == 0) //如果当前工作线程为0,增加非核心线程
addWorker(null, false);
}
else if (!addWorker(command, false)) //如果阻塞队列满了,增加非核心线程
reject(command); //如果非核心线程也满了,那就使用拒绝策略
}
addWorker() 上半部分
通过内外两层循环,先检查线程池当前可不可以增加新的worker,不可以增加新worker的情况有两大类:
- 线程池不在running状态
a). 线程池处于Stop, Tidying, Terminated
b). 线程池接受了新任务
c). 阻塞队列为空 - 线程池在running状态
a). 当前工作线程数已经达到了理论最大值0x1fffffff
b). 当前工作线程数超过了核心线程数或者最大线程数(取决于参数core的值)
简单的说,如果线程池不在running状态了,那线程池要想创建新worker,必须是在线程池shutdown状态,因为这个时候线程池虽然不接受新任务,但之前已接受的任务还都会处理完。而且这个时候,创建的worker只会是非核心线程,而且只会从阻塞队列里去拿任务。如果线程池还在running状态,那线程池数量必须在要求的限制范围内。
检查后,确定可以创建新worker,那就通过CAS将工作线程数加1,真正创建新worker要到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 && //如果线程池状态大于0(只有running状态是小于0的)
! (rs == SHUTDOWN && //如果线程池状态不是shutdown(shutdown是全0)
firstTask == null && //或者传入的task非空
! workQueue.isEmpty())) //或者阻塞队列是空
return false; //返回false
//内层循环
for (;;) {
int wc = workerCountOf(c); //获取当前工作线程数
if (wc >= CAPACITY || //如果当前工作线程数达到或者超过最大值
wc >= (core ? corePoolSize : maximumPoolSize)) //或者当前工作线程数超过核心线程数(如果是非核心,则和最大线程数比较)
return false; //返回false
if (compareAndIncrementWorkerCount(c)) //通过CAS增加工作线程数量,也就是线程池状态直接加1
break retry; //如果CAS成功的话直接跳出外层循环
//如果能执行到这一行说明上一步的CAS没有成功
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs) //如果前三位的线程池状态改变了,那就重新走外层循环
continue retry; //回到最开始retry的位置
// else CAS failed due to workerCount change; retry inner loop 否则就重新走内层循环
}
}
addWorker() 下半部分
实质的创建了新worker,并且启动线程,让这个worker开始工作。当前线程启动的条件是线程池还处于running状态,或者处于shutdown状态而且没有接受新任务。最后返回布尔值代表线程是否成功启动。
//外层循环
//内层循环 完成工作线程数加1
//执行到这一行说明CAS成功,线程池的当前工作线程数已经更新
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); //初始化Worker
final Thread t = w.thread; //获取Worker对象的线程
if (t != null) {
final ReentrantLock mainLock = this.mainLock; //加了一个Reentrantlock锁
mainLock.lock();
try {
int rs = runStateOf(ctl.get()); //获取线程池当前状态
if (rs < SHUTDOWN || //如果当前状态小于0 也就是处于running状态
(rs == SHUTDOWN && firstTask == null)) { //或者线程池处于shutdown状态以及task为空,走进if下面的语句
if (t.isAlive()) //如果线程已经启动,那就报异常
throw new IllegalThreadStateException();
workers.add(w); //workers是一个HashSet
int s = workers.size(); //获取workers的大小
if (s > largestPoolSize) //记录最大的线程数量
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); //启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w); //如果由于中断,而且这个线程还没启动,就会把这个线程给移除workers哈希表,以及将工作线程数量减1
}
return workerStarted; //返回线程是否成功启动
}
看看Worker构造器,将传入的firstTask赋给Worker对象的Runnable变量,再将Worker对象的线程初始化,但注意,这时还没有将firstTask交给线程处理
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
Worker类继承了Runnable接口,真正启动线程调用的就是Worker类里的run方法
public void run() {
runWorker(this);
}
runWorker()
如果worker的task是空的话,那就从阻塞队列里面去取task,并且这里是个while大循环,不断地去判断是不是有事务要做,实现了线程池的复用机制。如果在阻塞队列取task超时了,那就会跳出while往finally走,准备回收当前这个worker。
在阻塞队列取task的工作在getTask()
里完成;回收worker的工作在processWorkerExit()
里完成
final void runWorker(Worker w) {
Thread wt = Thread.currentThread(); //拿到当前线程
Runnable task = w.firstTask; //拿到业务
w.firstTask = null; //把worker里的firstTask置空
w.unlock(); //一开始new Worker的时候,state设的是-1,这里解锁就是将state设为0
boolean completedAbruptly = true; //true表示当前线程没有正常结束
try {
/** 循环复用,如果task是空的话,就从阻塞队列里面去取task
* 如果阻塞队列里没东西了,线程就会阻塞在那里,如果阻塞过了一定时间,就会返回null
* 然后跳出while循环,往finally走,准备回收当前这个worker
*/
while (task != null || (task = getTask()) != null) {
w.lock();
/**
* 如果线程池快已经是Stop或者Tidying或者Terminated了,那就中断当前线程
* 否则就检查当前线程有没有被中断,没有被中断的话就继续往下走
*/
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(); //这里调用了我们一开始调用线程池execute方法时候传进来的那个Runnable对象的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; //将task置空
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false; //能执行到这里说明阻塞队列获取task超时了,算是正常结束,准备回收worker
} finally {
processWorkerExit(w, completedAbruptly); //回收worker
}
}
getTask()
从阻塞队列里去取task。有3种情况会决定要回收当前worker:
- 线程池处于shutdown,而且阻塞队列为空。不会接受新任务了,也没有旧任务做了,回收
- 线程池处于stop。新旧任务都不会做了,回收
- 如果当前线程数超过了核心线程数(默认核心线程不要求回收),并且从阻塞队列取task超时,回收
注意,如果核心线程也要求回收(取决于allowCoreThreadTimeOut),那核心线程超时了也一样会被回收,哪怕最后所有线程全都回收了。
如果这里决定要回收worker了,会预先把工作线程数减1,然后由processWorkerExit()
真正执行回收工作。但如果由于中断等原因,这个worker应该要回收了,但工作线程数没有减1,那工作线程数减1的工作也会在processWorkerExit()
里补充完成。
private Runnable getTask() {
boolean timedOut = false; //用于判断从阻塞队列取task是否超时,超时为true
for (;;) {
int c = ctl.get(); //得到线程池状态
int rs = runStateOf(c); //得到线程池运行状态
/** 这几种情况会执行worker数量减1,也就是回收worker
* 1. 如果线程池处于shutdown,而且阻塞队列为空
* 2. 如果线程池处于stop
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount(); //worker数量减1,这个操作直接改变线程池状态值
return null; //返回null,回到runWorker方法,准备回收当前worker
}
//执行到这一步说明线程池是在running状态,worker数量也没有减1
int wc = workerCountOf(c); //得到当前工作线程数量
//allowCoreThreadTimeOut表示核心线程如果空闲超过keepAliveTime的话,要不要进行回收,默认为false,不需要回收
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //如果当前工作线程数量超过核心线程数则为true
/** 3. 如果当前线程数超过最大线程数或者超过核心线程数,并且从阻塞队列取task超时,则把工作线程数worker减1
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null; //返回null,回到runWorker方法,准备回收当前worker
continue;
}
//能执行到这里,说明worker还没有要被进行回收
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : //timed为true代表线程数超过了核心线程数,或者核心线程也需要超时回收
workQueue.take(); //timed为false代表当前worker是核心线程,而且不需要超时回收,所以如果它闲置着就让它一直等下去
if (r != null)
return r; //取到task
timedOut = true; //超时了,没取到task
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
processWorkerExit()
如果线程是非正常结束的,那就会先将工作线程数减1。然后从workers哈希表中移除当前这个worker。再尝试终止线程池。如果当前线程池处在shutdown或者running状态,那有两种情况可能会需要再次补充创建非核心线程:
- 当前worker是非正常结束的
- 当前worker是正常结束的,但当前线程池工作线程数量小于最少需求数
private void processWorkerExit(Worker w, boolean completedAbruptly) {
/** 如果completedAbruptly为true说明线程非正常结束,可能由中断引起,那就先工作线程数减1
* 如果是正常结束的话,在getTask()里已经完成了工作线程数减1
*/
if (completedAbruptly)
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)) { //如果是shutdown或者running状态
if (!completedAbruptly) { //如果线程是正常结束
int min = allowCoreThreadTimeOut ? 0 : corePoolSize; //找到当前线程最少需要的线程数
if (min == 0 && ! workQueue.isEmpty()) //如果核心线程要求也被回收,然而阻塞队列非空,那至少留1个线程
min = 1;
if (workerCountOf(c) >= min) //如果当前工作线程数量满足最少的数量要求,那就直接return,否则还需要增加工作线程
return; //直接返回,回到runWorker(), 也就结束了当前线程
}
addWorker(null, false);//增加非核心工作线程
//之后返回,回到runWorker(), 结束当前线程
}
}
tryTerminate()
线程池可以结束的条件:
- 线程池不在running状态
- 线程池不在shutdown状态;或者线程池在shutdown状态,但阻塞队列为空
线程池具备可以结束的条件之后,一次会尝试中断一个闲置线程(如果线程正在工作那就不会被中断),然后被中断的线程就会被回收,再去中断另一个线程,直到所有线程都被回收,线程池开始终止。
final void tryTerminate() {
for (;;) {
int c = ctl.get(); //得到线程池当前状态
if (isRunning(c) || //如果线程池在running状态,不能结束线程池
runStateAtLeast(c, TIDYING) || //或者线程池在TIDYING或者TERMINATED状态,那已经快结束了,不需要什么操作
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) //或者线程池在shutdown状态,而且阻塞队列不为空,不能结束线程池
return; //返回,还不能结束线程池
//执行到这里,说明具备线程池结束条件
if (workerCountOf(c) != 0) { //如果工作线程为0, 那说明全部回收好了
interruptIdleWorkers(ONLY_ONE); //中断一个闲置线程(如果线程正在工作那就不会被中断),之后它会被回收,然后再去中断另一个,直到全部回收完毕
return;
}
//线程全部回收完毕了
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { //CAS将线程池状态设为TIDYING
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0)); //CAS将线程池状态设为TERMINATED
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
shutdown()
将线程池转为shutdown状态,然后结束线程池
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess(); //检查关闭线程池的权限
advanceRunState(SHUTDOWN); //通过CAS把线程池状态设为shutdown
interruptIdleWorkers(); //中断所有的闲置线程
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate(); //尝试结束线程池
}
shutdownNow()
同样也是结束线程池,但和shutdown()
不一样的是:
shutdownNow()
有返回值,返回的就是阻塞队列里没完成的任务- 线程池状态先转为的是Stop
- 会中断所有的线程,包括正在工作的
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP); //线程池状态设为stop
interruptWorkers(); //中断所有线程,包括正在工作的线程
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
七、总结
线程池原理简单的讲就和银行办理业务的流程很相似,一开始只有核心线程受理业务,核心线程全部开启了,任务就会进阻塞队列排队,阻塞队列排满了,就会开启非核心线程,非核心线程也满了,就会启动拒绝策略。
每个工作线程处理好手头的业务之后,都会循环的去阻塞队列里取新任务来执行,实现线程的复用。
任务减少的时候,非核心线程如果长时间没有拿到任务就会被回收。但是核心线程如果不要求回收,即使没有任务,它们也会一直待在线程池里。
需要注意的是,在线程池内部,其实并没有对每个线程进行核心/非核心线程的标记。核心/非核心线程仅仅是在数量上进行了定义,而这个数量的限制规定了我们什么时候要创建新的工作线程,什么时候回收旧的线程,以及线程池里应该留下多少线程。