Worker.run() 中的 while 循环复用同一线程持续消费任务:先执行初始 firstTask,再循环调用 getTask() 阻塞获取任务;getTask() 返回 null(shutdown、stop 或空闲超时)时循环退出,线程终止。

Worker.run() 里那个 while 循环到底在循环什么
线程复用的关键,就藏在 Worker 类的 run() 方法里——它没执行完就退出,而是持续从阻塞队列取任务。这个循环不是“反复启动新线程”,而是同一个 Thread 对象反复调用 task.run()。
常见错误现象:以为线程池 submit 一次就新建一个线程;结果发现线程数稳定在 corePoolSize,但任务还在跑——其实是同一个线程在循环消费。
-
Worker初始化时会绑定一个初始firstTask,执行完才进入循环 - 循环体里调用
getTask(),该方法会阻塞等待(比如用LinkedBlockingQueue.poll(timeout)) - 如果
getTask()返回null(超时、线程池 shutdown、或达到 maximumPoolSize 后空闲超时),循环退出,线程自然结束 - 注意:
runWorker(Worker w)是实际执行循环的入口,Worker.run()只是委托给它
getTask() 返回 null 的三种典型时机
线程复用不是无限的,getTask() 返回 null 就意味着这个 Worker 要退出了。这不是 bug,是设计好的回收机制。
容易踩的坑:把 allowCoreThreadTimeOut = true 和 keepAliveTime = 0 混用,导致 core 线程也立刻销毁,任务排队却没人处理。
立即学习“Java免费学习笔记(深入)”;
- 线程池已
shutdown,且工作队列为空 → 立即返回null - 线程池已
stop→ 不管队列是否为空,直接返回null - 线程空闲超时:当前线程数 > corePoolSize,且从队列 poll 超过
keepAliveTime未拿到任务 → 返回null
为什么 Worker 继承 AQS 却不用锁?
Worker 继承 AbstractQueuedSynchronizer,但没用它做互斥锁,而是借用了其 state 字段和 CAS 原语,实现线程中断状态的原子控制。
使用场景:当调用 thread.interrupt() 时,需确保不干扰正在运行的任务(比如任务自己也在调用 interrupt()),也不能漏掉中断信号。
-
Worker用AQS.setState(0)表示“允许中断”,setState(1)表示“运行中禁止中断” -
runWorker()开始执行任务前调用interruptIfStarted(),只在 state == 1 时才真正中断线程 - 如果任务本身调用了
Thread.sleep()或queue.take(),中断会抛出InterruptedException,此时线程不会立即退出,而是继续下一轮循环 —— 这正是复用的前提
自定义 ThreadFactory 时最容易忽略的点
很多人以为只要重写 newThread() 就能控制线程行为,但忘了 Worker 在构造时就把线程的 target 设为自身(Runnable),而不是用户传的任务。
后果:如果你在线程工厂里给线程设了 setUncaughtExceptionHandler,它确实生效;但若试图通过 setContextClassLoader 影响任务执行环境,大概率失效——因为任务是在 Worker.run() 内部调用的,上下文类加载器取决于 Worker 所在线程创建时的快照,不是你 newThread 时设的那个。
- 务必在
newThread()中设置有意义的线程名,如"biz-pool-worker-" + counter.getAndIncrement(),否则堆栈里全是pool-1-thread-1 - 不要依赖线程工厂修改线程优先级——JVM 规范不保证跨平台有效,且可能被容器或安全策略拦截
- 若需传递上下文(如 TraceId),不能靠线程局部变量初始化,得在
execute()或submit()时显式包装任务(例如用new ThreadLocalContextWrapper(task))








