线程池
线程池
前言
面试官:我们刚刚说了这么多aqs,其实他本质也就是一个线程同步的抽象类,延伸出了很多线程同步的工具类,那么关于线程池中也有相关的aqs应用,你知道吗?
我:啊?线程池还有这个应用吗,没太了解!
面试官:那看来你没太深入啊!那这样吧,我问简单点的问题,线程池最基本的参数有哪些?
我:好嘞!(心中暗暗自喜,八股文开始)巴拉巴拉!
面试官:好家伙!我没问的你也回答了,那你刚刚说的线程池运行的流程回答的正确,那现在你说下线程池有哪些状态?
我:(啊,线程池状态?我哪里知道,不会是线程的6个状态吧,又好像不是…)额,应该有个Running,还那个啥啥啥?!!!
面试官:好吧,咱们下一个问题吧,这边也不多问了!
我:…
1.池化技术
池化技术 (Pool) 是一种很常见的编程技巧,在请求量大时能明显优化应用性能,降低系统频繁建连的资源开销。我们日常工作中常见的有数据库连接池、线程池、对象池等,它们的特点都是将 “昂贵的”、“费时的” 的资源维护在一个特定的 “池子” 中,规定其最小连接数、最大连接数、阻塞队列等配置,方便进行统一管理和复用,通常还会附带一些探活机制、强制回收、监控一类的配套功能。
简而言之就是为了更好的利用资源进行管理。
常见的池化技术:
- 线程池
- 连接池
- 内存池
- …
2.源码分析
构造参数相关的意义以及相关线程池运行的规则流程都是比较简单的,不细讲了
2.1线程池状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//Integer.SIZE是32 也就是COUNT_BITS=29
private static final int COUNT_BITS = Integer.SIZE - 3;
//1右移29位-1=2^29-1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//同理右移29
private static final int RUNNING = -1 << COUNT_BITS;
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;
//异或CAPACITY = 当前线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//与CAPACITY = 当前工作的线程总数
private static int workerCountOf(int c) { return c & CAPACITY; }
//与re 或 wc = 合成一个整数
private static int ctlOf(int rs, int wc) { return rs | wc; }
其实线程池中用一个ctl 去维护
- 线程池状态 前3位
- 运行线程数 后29位(这里也说明了线程池最大运行线程数是29位)
接下来线程状态相关的5种以及转化过程
2.2execute流程分析
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获取当前状态变量
int c = ctl.get();
//如果工作线程数<核心线程数
if (workerCountOf(c) < corePoolSize) {
//加入一个线程
if (addWorker(command, true))
return;
//重新获取当前状态
c = ctl.get();
}
//线程池如果是Running并且加入阻塞队列成功
if (isRunning(c) && workQueue.offer(command)) {
//再次重新获取当前状态
int recheck = ctl.get();
//再次检查运行状态是否正确,如果错误,移除刚刚加入的任务,并拒绝
if (! isRunning(recheck) && remove(command))
reject(command);
//工作线程数==0
else if (workerCountOf(recheck) == 0)
//添加一个线程数
addWorker(null, false);
}
//线程池不是Running或者加入队列失败,并且加入线程失败,就去拒绝
else if (!addWorker(command, false))
reject(command);
}
接下来看看addWorker(command, false)方法
/**
* @param 任务
* @param 是否是核心线程
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//自旋1
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//1.如果线程池是shutdown状态,并且满足(firstTask!=null或者队列是空),则不创建
//STOP,TIDYING,TERMINATED状态也都不出创建
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//自旋2
for (;;) {
int wc = workerCountOf(c);
//如果当前线程数量>=最大值
//或者
//根据当前加入的是否是核心线程来判断当前线程数量>=改线程数
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//说明线程数还没达到最大值,cas增加当前线程池变量ctl中的线程数
if (compareAndIncrementWorkerCount(c))
//退出当前循环
break retry;
//说明没增加到,ctl有变化,重新去获取
c = ctl.get();
//如果线程池状态还没变化,回归自旋2,否则回归自旋1
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//线程池全局锁上,防止线程池其他变化
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
//线程池状态=running
//或者
//线程池状态=shutdown并且加入的firstTask==null
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//去检测线程状态,这一步我实在没想到什么时候会发生
if (t.isAlive())
throw new IllegalThreadStateException();
//添加到我们的workers中
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;
}
正常的添加到任务队列已经基本讲完了,那最后也可能没添加进去,需要去主动回滚一些数据,所以来看下addWorkerFailed()
private void addWorkerFailed(Worker w) {
//获取全局锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//移除线程集合
if (w != null)
workers.remove(w);
//ctl中线程数-1
decrementWorkerCount();
//尝试去终止
tryTerminate();
} finally {
mainLock.unlock();
}
}
2.3worker对象
其实在上面执行流程中,我们启动了一个线程,然后这个线程被保存在workers里面
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
线程池中肯定维护着很多的线程,对于这种线程对象,线程池采用Worker对象去扩展一些线程的属性以及一些方法,在后续的线程池管理中能够高效安全的去维护线程池的管理。但是这个线程是怎么执行任务的,又是怎么控制的,这边都是疑问,所以我们来看下Worker类
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
//第一次执行的任务
Runnable firstTask;
//完成任务的数量
volatile long completedTasks;
//构造方法
Worker(Runnable firstTask) {
//禁止中断线程,和下面interruptIfStarted()联系在一起,
setState(-1)
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
//该线程执行的任务
runWorker(this);
}
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
//中断线程
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
runWorker()代表该任务具体的执行内容
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//允许线程可以被中断,可以关注tryRelease方法,设置了state=0
w.unlock();
boolean completedAbruptly = true;
try {
//不断去循环获取任务,首先获取firstTask,
//上面如果是空,然后在去getTask(),从任务队列里获取任务
while (task != null || (task = getTask()) != null) {
//worker对象的锁,为什么要锁?主要原因是在某些情况下防止在执行时候线程被中断
w.lock();
//1.线程池状态最少是STOP时候,线程没被中断,就中断线程
//2.线程池shutdownNow时候需要清除中断状态
//有待深入研究
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//子类实现的执行前的方法
beforeExecute(wt, task);
Throwable thrown = null;
try {
//执行具体的任务,实际的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 {
processWorkerExit(w, completedAbruptly);
}
}
先来看看获取阻塞队列的代码
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果是SHUTDOWN并且队列是空
// 或者如果是STOP,TIDYING,TERMINATED其中之一
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//一般allowCoreThreadTimeOut都是false
//所以这边也就是当前线程数是否>核心线程数
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//用来超时处理返回(具体没看懂)
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
//去动态获取队列
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
然后继续讲下面的
如果是正常的核心线程数,没有其他设置,应该是不会超时,他就会一直阻塞去获取任务队列
如果是非核心线程数,他会有超时时间,然后返回一个null,从而跳出循环,设置completedAbruptly=false
接下来我们看processWorkerExit方法
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//如果被中断,设置ctl减1,=false时候,是超时的表现,getTask()已经减1了,这边不用处理了
if (completedAbruptly)
decrementWorkerCount();
//全局锁去统计完成总数和移除队列
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试去终止
tryTerminate();
int c = ctl.get();
//如果是RUNNING 或者 SHUTDOWN
if (runStateLessThan(c, STOP)) {
//如果是超时引起的
if (!completedAbruptly) {
//最小线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//工作队列有任务,最小线程数是1
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//是否达到最小线程数
if (workerCountOf(c) >= min)
return; // replacement not needed
}
//添加一个线程
addWorker(null, false);
}
}
2.4shutdown流程
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//检查是否有权限,主要是安全管理器
checkShutdownAccess();
//设置SHUTDOWN状态
advanceRunState(SHUTDOWN);
//中断所有线程
interruptIdleWorkers();
//相关关闭方法
onShutdown();
} finally {
mainLock.unlock();
}
//
tryTerminate();
}
最关键的方法也就是interruptIdleWorkers()
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
这边看完后发现就是循环去中断,但是他这边也是用tryLock()去获取锁,所以联系起上面执行具体run()方法也就清楚了,这边打断是让线程执行完后获得锁后再去中断
2.5shutdownNow流程
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
//中断所有线程
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
他这边和shutdown差不多,就一点区别,我们就分析这一段interruptWorkers();
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
interruptIfStarted()在点开看下
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
也就明白了,在启动线程前,我们需要setState(-1),就是为了防止中断,而shutdownNow()就直接中断所有线程哪怕没有获取到锁。
2.6tryTerminate流程
tryTerminate()尝试去终止
final void tryTerminate() {
for (;;) {
int c = ctl.get();
//1.如果是RUNNING
//2.是TIDYING或者TERMINATED
//3.是SHUTDOW,并且队列不是空
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 说明当前是stop,或者SHUTDOWN并且队列是空
// api中有这么一段话
// If otherwise eligible to terminate but workerCount is nonzero,
// interrupts an idle worker to ensure that shutdown signals propagate.
// 当满足终结线程池的条件但是工作线程数不为0,
// 这个时候需要中断一个空闲的工作线程去确保线程池关闭的信号得以传播。
// 此时一个线程被中断后去调用processWorkerExit()从而进行传播
if (workerCountOf(c) != 0) {
//减少最多一个线程
interruptIdleWorkers(ONLY_ONE);
return;
}
//全局加锁去进行终止
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
最主要还是做了3件事情
- 判断线程池状态
- 线程数-1
- 更新线程池状态
3.总结
其实相对来说疑问点都可以理解了
我就提出几个相关小问题来总结
线程池的核心线程能被销毁吗?
线程池状态的变化?
线程池中的Worker为什么要实现AQS?
线程池是如何维护相关的状态?
当队列满的时候,在此添加一个任务,那么这时候是怎么取任务的?是从队列头中取任务,然后把当前任务塞回去?还是把当前任务作为第一个任务?