Java线程池之所以能用固定数量的线程处理大量任务,关键在于线程执行完一个任务后不会被销毁,而是不断从阻塞队列中获取新任务继续执行。这套复用机制的核心实现位于ThreadPoolExecutor的内部类Worker以及它的runWorker方法中。Worker本身既实现了Runnable接口,又继承了AbstractQueuedSynchronizer,用来标识线程是否正在执行任务。下面直接结合源码看线程是如何被包装并进入循环取任务的。

一、Worker类的基本结构与线程包装
Worker是ThreadPoolExecutor的私有内部类,它在构造时会接收一个初始任务firstTask,并调用ThreadFactory创建一个新线程,把自身作为Runnable传入。因为Worker实现了Runnable,所以线程启动后实际运行的就是Worker的run方法。
Worker同时继承了AQS,但并没有使用完整的同步器语义,只是利用state字段表示锁状态:0代表未加锁,1代表加锁。它实现了tryAcquire和tryRelease来控制线程执行任务期间的独占,这样在中断空闲线程时可以通过tryLock判断线程是否正在忙。如下是简化版的Worker结构代码:
final class Worker extends AbstractQueuedSynchronizer implements Runnable {
private static final long serialVersionUID = 1L;
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // 禁止中断,直到runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
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;
}
}
从上面可以看出,Worker将线程和任务绑定在一起,但它并不是执行完firstTask就结束,而是把接力棒交给了外部的runWorker方法。runWorker是真正驱动线程复用的入口。
二、runWorker方法中的循环取任务逻辑
runWorker在ThreadPoolExecutor中定义,它先执行第一个任务,之后进入无限while循环,不断通过getTask从workQueue阻塞队列中获取新任务。只要getTask返回非空,就会调用task.run让当前线程执行。这样同一个线程就能串行处理多个任务,实现复用。
在每次执行任务前后,Worker会调用lock和unlock,目的是标记线程处于忙状态,避免被超时回收逻辑误中断。同时beforeExecute和afterExecute提供了扩展点。以下是runWorker核心片段:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 允许中断
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (Throwable x) {
thrown = x;
throw x;
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
这段代码清晰地展示了复用模型:线程启动后先跑firstTask,随后只要getTask能从队列拿到任务,就一直循环。如果队列空且符合回收条件,getTask返回null,循环退出,线程才会真正结束。这种设计避免了频繁创建销毁线程的开销。
三、getTask如何从阻塞队列获取任务
getTask方法决定了线程在队列空时的行为:是阻塞等待还是超时退出。它会根据当前线程数是否大于核心线程数,选择不同的队列取数策略。如果允许核心线程超时或者当前线程数超过corePoolSize,就使用poll并带keepAliveTime;否则使用take无限阻塞。
下面是getTask的精简逻辑,展示了阻塞与超时两种分支:
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
// 检查线程池状态,若已关闭则减少计数并返回null
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
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;
}
}
}
当使用take时,如果队列中没有任务,当前线程会挂起在阻塞队列的条件变量上,直到其他线程提交任务并唤醒它。这就是为什么核心线程在没有任务时也不会退出,而是安静地等着被复用。当使用poll超时模式时,超过存活时间没拿到任务就返回null,runWorker循环结束,线程销毁。
四、线程复用机制的优缺点与调优思考
这种基于Worker循环加阻塞队列的复用方式,最大优点是极大降低了线程创建销毁的系统开销,尤其适合大量短平快的任务场景。由于所有任务都在同一线程内顺序执行,也避免了多线程切换的混乱,配合线程池的拒绝策略可以平稳控制负载。
但在实际使用中,如果核心线程数设置过大且allowCoreThreadTimeOut为false,即使系统空闲也会常驻大量线程,占用内存。反之如果队列无界,可能导致任务堆积引发OOM。理解Worker和getTask的源码后,就能更有依据地设置corePoolSize、keepAliveTime以及选择有界或无界队列,让线程复用既高效又安全。