【从问题开始】
在使用线程池的时候往往会看到线程池reject相关日志,例如以下日志:
[Running, pool size = 80, active threads = 3, queued tasks = 3, completed tasks = 1674]
遂产生疑问:为什么线程池大小为80,队列长度为3的线程池,只有3个active threads就会触发reject机制?
在第二篇分析拒绝处理机制时了解到:
- nactive:活跃线程数量,实际上是取的worker中处于isLocked状态的数量。
同时在第三篇分析addWorker方法时发现线程池实际将Runnable的执行委托给了Worker。结合场景,需要进一步了解Worker在线程池中扮演的角色。
先看Worker类的声明:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
可以发现,Worker继承自AQS,实现Runnable接口,基本可以了解Worker本质是一个带锁的可执行对象。
看Worker的核心成员变量:
// 负责执行的线程
final Thread thread;
// Worker要执行的任务
Runnable firstTask;
// Worker执行完成的任务,具备线程统计的能力
volatile long completedTasks;
同时还有一部分继承自AQS的成员变量:
// 阻塞队列的头节点
private transient volatile Node head;
// 阻塞队列的尾节点
private transient volatile Node tail;
// 锁状态
private volatile int state;
// 继承自AbstractOwnableSynchronizer的持锁对象变量
private transient Thread exclusiveOwnerThread;
再看Worker的构造函数:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
既然Worker继承自AQS,那么一定会重写AQS锁竞争释放相关的代码,同时继承AQS状态相关的属性。在构造函数中就可以看出这一点。setState方法首先将AQS状态设置为-1。这里先放下state不看,后面的firstTask即线程池execute方法传入的Runnable任务,thread是新建的执行线程
Worker中与AQS相关的代码如下:
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
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(); }
isLocked方法和isHeldExclusively方法在第二篇中已经见过一面
for (Worker w : workers) {
ncompleted += w.completedTasks;
if (w.isLocked())
// 这里可知nactive实际取的是加锁成功的worker数量,而非有一个算一个
++nactive;
}
[Running, pool size = 80, active threads = 3, queued tasks = 3, completed tasks = 1674]
因此实际上在日志中输出的active threads是锁state状态不为0的Worker。
那么state的值都有什么呢?
可以看到lock方法实际上调用的是AQS中的acquire方法,tryLock调用的是子类覆盖的tryAcquire方法,实际在AQS中调用的底层方法都是tryAcquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在AQS中acquire方法的作用是先尝试调用子类的tryAcquire方法,如果获取不到锁,则将AQS节点加入到AQS阻塞队列中,同时尝试取出阻塞队列的队头,开启一个死循环尝试获取锁,如果获取不到,将节点的状态修正为SINGNAL(等待唤醒)同时对持有线程做park操作,等待持锁线程调用tryRelase方法将锁释放,同时唤醒队头的持有线程,继续循环获取锁。
注:这里的节点状态是AQS中的Node节点状态,即waitStatus属性,而非AQS的同步锁状态state属性,这里是一个非常容易混淆的地方。state属性是前面提到的Worker统计isLocked时使用的属性,同样也是后面加锁和释放锁使用的属性。
对于AQS的操作流程后续另行记录。此处先看Worker对tryAcquire的实现。
既然AQS将tryAcquire交给子类实现,正体现了锁的多样性。Worker对其实现就是典型的不可重入锁。
// Worker的不可重入锁实现
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// ReentrantLock类公平锁的tryAcquire实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
final void lock() {
acquire(1);
}
这里对比ThreadPoolExecutor中Worker类和ReentrantLock中FairSync公平锁的实现。
Worker是典型的不可重入锁,使用CAS判断State状态为0,即设置为1,表示已加锁成功,然后设置持锁线程为当前线程,即说明已加锁成功,返回true
FairSync是典型的可重入锁,首先获取自己的state,这里有两个场景:
- 如果是0,还需要判断此时队列中已没有阻塞的等锁节点(这里是公平锁和非公平锁的差异,公平锁需要判断阻塞队列无任务等锁,非公平锁则无需判断队列,直接自己尝试获取),满足条件,执行CAS判断当前状态是0,修正为acquires,从方法的调用方可以看出,acquire传入是1,即由0变为1。变更成功则设置持锁线程为自身,返回true
- 如果state不是0,这里要先判断持锁线程是不是自身,如果是自身,拿到的state与传入的acqires相加,即state += 1,然后再设置state值。这就实现了每次当前线程拿锁,state值自增1.
同样的,Worker和FairSync的tryRelase方法实现如下:
// Worker中的不可重入的tryRelease方法实现
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// ReentrantLock公平锁的可重入的tryRelease方法实现
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
public void unlock() {
sync.release(1);
}
Worker的tryRelease方法实现很简单,把持锁线程置空,把state置0即可。
ReentrantLock公平锁的tryRelease方法则同样针对可重入的特性,获取state减掉release,从调用点来看即state -= 1,直到state为0时,才判断为已释放锁,设置持锁线程为null。
综上我们可以得出结论:对于不可重入锁Worker,state状态有初始化时的-1、未加锁状态0、加锁状态1。因此nactive实际体现的是1状态的数量。-1状态实际上初始化后会被立即抹掉。这就需要继续了解Worker的Runnable特性了。
由此,今天的分析也引出了以下几个问题:
由此我们提出以下几个问题,作为后续分析的方向:
- Runnable特性是如何运作的?
- Worker的state成员变量的变化流程是什么样的?
本篇结论:
- Worker继承AQS同时实现了Runnable接口,本质上是一个带锁的可执行对象。
- Worker的state有-1、0、1三种状态,而线程池对于Worker的统计,active worker数量实际是state为0的状态
- Worker是一个不可重入锁,实现了锁相关的tryAcquire和tryRelease方法。