【从问题开始】
在使用线程池的时候往往会看到线程池reject相关日志,例如以下日志:
[Running, pool size = 80, active threads = 3, queued tasks = 3, completed tasks = 1674]
遂产生疑问:为什么线程池大小为80,队列长度为3的线程池,只有3个active threads就会触发reject机制?
第四篇分析Worker的AQS属性基本了解到active threads的值取自nactive属性,实际取的是AQS中的state属性非0的,即自身已完成加锁的Worker。本篇继续追溯Worker从创建到加锁的流程,深入了解下它实现Runnable属性的流程。
目录
Thread和run方法
Runnable接口提供了唯一的接口方法Run方法
public interface Runnable {
public abstract void run();
}
Runnable最常用典型的实现就是Thread类。Thread类的核心成员变量包括:
// 线程名称
private volatile String name;
// 线程上下文
ThreadLocal.ThreadLocalMap threadLocals = null;
// 线程组,存储执行线程
private ThreadGroup group;
// 线程执行的可执行单元(线程执行谁)
private Runnable target;
此处不关注Thread的状态(当然Thread状态本身也是非常重要的知识点),仅针对问题的本身,即Thread和run方法的调用。
Thread本身提供了带参构造
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
……
this.target = target;
……
}
可见传入的Runnable对象实际被存入了target属性中
使用过Thread类应该熟悉,新创建的线程通过Thread::start方法启动。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
可以看到在start方法中,除了一些状态的处理,实际启动的地方即start0方法
private native void start0();
start0是一个native方法,它的实现包括:
- 首先,它会检查当前线程对象的状态是否允许启动。如果线程对象的状态不满足启动条件,start0()方法将抛出一个IllegalThreadStateException异常。
- 接下来,start0()方法会为新线程分配所需的系统资源和内存空间。这包括为线程创建一个独立的执行环境和堆栈空间。
- 一旦系统资源和内存分配完成,start0()方法将调用操作系统提供的原生函数,以便创建一个真正的操作系统级线程。这个原生函数可能是基于平台的API,比如Windows的CreateThread()或Linux的pthread_create()。
- 最后,start0()方法会更新线程对象的状态,并且将线程添加到线程调度器中,使其可以被调度执行。
同时,在start0的执行过程中,JVM会自动调用线程thread的run方法。因此在jdk源码中,实际看不到run方法的调用,但其实在start方法触发时就执行了run方法。
看Thread的run方法:
public void run() {
if (target != null) {
target.run();
}
}
本身是非常简单的,即执行target的run方法。看到前面的核心成员属性,target实际上就是一个Runnable对象。既然Thread本身就实现了Runnale,为什么还要有一个Runnable的成员变量呢?
这里应该是为了实现执行线程和可执行任务之间的分离,从而不会让可执行任务被强制绑定在一个执行线程上。如果线程自带run,那么创建多个可执行任务就是多个thread,只能一个执行一个,而通过创建一个Runnable,可以给多个线程执行,对一个Runnable进行并发处理。例如银行取号机,四台取号机共用一天的1-50号码序列,要对一个可执行单元做并发。
Worker和runWorker方法
了解了Thread的的run方法的前世今生,再来看Worker的run方法
首先回顾ThreadPoolExecutor中的addWorker方法的一段代码
// 1.首先执行worker的构造方法
w = new Worker(firstTask);
// 2.取出worker的执行线程
final Thread t = w.thread;
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());
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) {
// 3.执行线程启动执行
t.start();
workerStarted = true;
}
}
首先执行的是worker的构造方法
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
这里再看Worker的构造方法,就有了一种豁然开朗的感觉,Worker实际上是一个可执行对象的承载对象,实际执行的内容是里面的task,Thread是Worker的执行线程,Worker构造时同时构造一个新Thread,同时将自身传入进去,存入Thread的target变量,同时Thread本身也被存储在Worker的thread变量,做为Worker的执行线程
然后取出Thread t = w.thread,到下面真正执行的地方是t.start()方法,进而调用到thread的run方法
回看上文可知,thread的run方法实际调用的是target的run方法,因此实际调用到了Worker的run方法
public void run() {
runWorker(this);
}
至此终于来到了Worker中最核心的runWorker方法
方法开始先取出并情况了firstTask变量,即构造时传入的task,执行后就清掉
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
然后立即执行执行unlock方法
public void unlock() { release(1); }
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
实际上通过AQS调用到自己重写的tryRelease方法,把自己的AQS状态设置为0,因此,初始化的AQS状态(-1)到0之间的时延实际上就是ThreadPoolExecutor::addWorker方法中Worker初始化到thread.start之间的时延
回到第四篇那个问题:nactive取的是workers中AQS非0的Worker的数量,为什么不会有-1被取到呢,概率应该是非常非常小的(这里为个人推断,欢迎指正与探讨),看addWorker就能看到其原因:
// 1.首先执行worker的构造方法,Worker的AQS状态为-1
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// 2.mainLock加锁,toString方法就不可能再拿到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());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 3.worker加入workers,这里才能被统计到
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 4.执行线程启动执行,Worker的AQS状态为0,加上锁变成1
t.start();
workerStarted = true;
}
}
首先Worker构造出来是-1状态,这时还没有加入workers,第二篇中看的toString方法统计nactive时统计不到;接着mainLock加锁,toString方法就势必不会再拿到锁了,因此也不会再执行统计操作;在持锁期间,worker加入workers,然后释放锁,立即执行了start方法,可见worker处于-1状态的大部分时间都在持锁时间段内,被取到-1状态的可能性应该是不会太大的
回到runWorker方法中,选定执行任务
while (task != null || (task = getTask()) != null) {
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();
} 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();
}
}
任务的选取首先判定的是传入的task,如果未传入task,通过getTask方法从阻塞队列中取task,后面的代码基本就非常好理解了
另一个比较重要的点就是getTask方法,本身涉及线程池中线程的启停,与AQS的condition产生一定的关联,后续将通过一个新问题引入到这里
本篇结论:
- Thread和Worker都是Runnable的实现类,在线程池中Worker的run方法通过addWorker方法在Thread::run方法中启动,而Thread::run方法是通过Thread::start方法通过jvm启动
- Worker构造时AQS的state为-1,在runWorker方法中首先解锁成为0,然后加锁成为1,在abortPolicy中输出的是即state非0的数量
至此ThreadPoolExecutor逻辑链梳理章节完成,本系列通过一个线程池的拒绝日志开始:
[Running, pool size = 80, active threads = 3, queued tasks = 3, completed tasks = 1674]
第一篇分析了线程池的构造和execute方法,发现worker是通过addWorker方法的返回值确认是否能过执行的,从而引出了线程池满时的拒绝策略和worker
第二篇分析了线程池的拒绝策略,了解了abortPolicy触发时输出日志是通过toString方法输出的,发现统计对象workers中的worker有一个加锁的流程,nactive实际统计的是加上锁的数量,发现Worker是具备AQS锁属性的
第三篇分析了addWorker方法,了解了一个Worker被传入后的创建流程,以及核心线程池和非核心线程池的概念,发现Worker具备Runnable属性,在run方法中触发了与AQS的交互
第四篇聚焦到Worker,了解了Worker的AQS属性,加解锁逻辑与ReentrantLock进行了对比
本篇同样聚焦到Worker,了解了Worker的Runnable属性,对Worker的run方法启动以及与AQS锁的交互进行了梳理。