导读:本期聚焦于小伙伴创作的《Java线程池中的线程是如何复用的?Worker类内部循环获取阻塞队列任务源码剖析》,敬请观看详情。线程池创建出来的核心线程在执行完第一个任务后并不会销毁,而是持续从阻塞队列中取任务执行,这种机制靠的是Worker类里的runWorker方法。该方法通过无限循环调用getTask从队列拉取任务,当返回为空时才退出循环并结束线程。理解这套复用逻辑,要重点看Worker如何实现AQS独占锁、线程如何被包装进Worker,以及getTask在核心线程超时策略下的不同表现。弄清这些源码细节,就能解释为什么线程池能用少量线程处理海量异步任务,也方便在调优时合理设置空闲存活时间。

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

Java线程池中的线程是如何复用的?Worker类内部循环获取阻塞队列任务源码剖析

一、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以及选择有界或无界队列,让线程复用既高效又安全。

Java线程池Worker类阻塞队列修改时间:2026-08-01 21:12:28

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。