Thread
Thread 源码分析
Thread 是 OS 内核线程在 Java 里的映射:JVM 层的一个对象句柄,背后挂着一条真正由内核调度的执行流。
它要解决的问题是把”创建/启动/中断/等待结束”这些平台相关的操作收敛成一套跨平台 API,核心思路是 Java 侧只管状态与协作协议,真正干活的全是 native 方法(start0/interrupt0/sleepNanos0)。
JDK 21 引入虚拟线程后,这个类被大改过一轮:平台线程专属字段全被挪进了 FieldHolder。
// 基于本地 JDK 源码仓 (JDK 25), java.base/java/lang/Thread.java
public class Thread implements Runnable {
private volatile long eetop; // 存底层 VM JavaThread 的内存地址。启动时置非 0,终止时归 0 —— isAlive() 就是判它
private final long tid; // 线程 id,全局自增,终身不变
private volatile String name;
volatile boolean interrupted; // 中断标志。就是个 volatile boolean,VM 和 Java 代码都会读写它
// ...
// 平台线程专属字段全在这里。虚拟线程的 holder 为 null —— 百万级虚拟线程不用为这些字段付内存
private static class FieldHolder {
final ThreadGroup group;
final Runnable task; // 构造时传入的 Runnable,run() 跑的就是它
final long stackSize;
volatile int priority;
volatile boolean daemon;
volatile int threadStatus; // VM 维护的状态字段,0 表示 NEW。getState() 从这翻译出 6 种状态
// ...
}
private final FieldHolder holder;
// ThreadLocal 的存储真身:map 挂在线程对象上,ThreadLocal 实例只是查这张 map 的 key → 天然线程隔离
private ThreadLocal.ThreadLocalMap threadLocals;
private ThreadLocal.ThreadLocalMap inheritableThreadLocals;
// 平台线程的真正构造入口,所有 public 构造器最终都走到这
Thread(ThreadGroup g, String name, int characteristics, Runnable task, long stackSize) {
Thread parent = currentThread();
boolean attached = (parent == this); // 只有 JVM 初始线程/JNI attach 会走 attached 分支
if (attached) {
// ...
} else {
if (g == null) {
g = parent.getThreadGroup();
}
// 优先级和 daemon 都从父线程继承 —— 所以在 daemon 线程里 new 出来的线程默认也是 daemon,这是个经典坑
int priority = Math.min(parent.getPriority(), g.getMaxPriority());
this.holder = new FieldHolder(g, task, stackSize, priority, parent.isDaemon());
}
// ...
if (!attached) {
if ((characteristics & NO_INHERIT_THREAD_LOCALS) == 0) {
ThreadLocal.ThreadLocalMap parentMap = parent.inheritableThreadLocals;
if (parentMap != null && parentMap.size() > 0) {
// InheritableThreadLocal 的继承发生在构造时(拷贝一份),不是 start 时 —— 线程池里复用线程时它不会刷新
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parentMap);
}
// ...
}
// ...
}
// ...
}
}// 基于本地 JDK 源码仓 (JDK 25), java.base/java/lang/Thread.java —— 启动与状态
public class Thread implements Runnable {
// start 只能调一次。真正的"创建 OS 线程"在 native 的 start0 里,run() 由新线程回调
public void start() {
synchronized (this) { // 锁 this 防止两个线程同时 start
if (holder.threadStatus != 0) // 非 0 说明已启动过(哪怕已经跑完),再调直接抛异常
throw new IllegalThreadStateException();
start0();
}
}
private native void start0(); // 向内核要线程的地方。它异步启动新执行流,由新线程回调 run()
// run 只是普通方法!手动调 thread.run() 就在当前线程同步执行,不会开新线程
@Override
public void run() {
Runnable task = holder.task;
if (task != null) {
Object bindings = scopedValueBindings();
runWith(bindings, task); // 最终就是 op.run(),多出来的是 ScopedValue 绑定的栈上锚定
}
}
public final boolean isAlive() {
return alive();
}
boolean alive() {
return eetop != 0; // 活着 = VM 侧 JavaThread 还在。比查状态枚举更底层、更快
}
// 6 种状态,是 JVM 视角的状态机,和 OS 线程状态不一一对应
public enum State {
NEW, // 还没 start
RUNNABLE, // 可运行。OS 层面的"就绪"和"运行中"Java 不细分,都算 RUNNABLE
BLOCKED, // 等 monitor 锁(synchronized 抢锁失败)
WAITING, // 无限期等待:wait()/join()/LockSupport.park(),要靠别人唤醒
TIMED_WAITING, // 限时等待:sleep(n)/wait(n)/join(n)/parkNanos
TERMINATED; // 跑完了,不可复活
}
public State getState() {
return threadState();
}
State threadState() {
return jdk.internal.misc.VM.toThreadState(holder.threadStatus); // 状态真身是 VM 写的 threadStatus 位,这里只做翻译
}
}// 基于本地 JDK 源码仓 (JDK 25), java.base/java/lang/Thread.java —— 中断、sleep、join
public class Thread implements Runnable {
// 中断 = 协作式通知,不是强杀。做两件事:置标志位 + 叫醒可能阻塞着的目标线程
public void interrupt() {
interrupted = true; // 必须先置标志再通知,否则被唤醒的线程可能看不到中断原因
interrupt0(); // native:通知 VM,把阻塞在 sleep/wait/park 上的线程唤醒
if (this != Thread.currentThread()) {
Interruptible blocker;
synchronized (interruptLock) {
blocker = nioBlocker; // 线程若阻塞在 NIO 可中断通道上,走通道自己的中断协议(关通道+抛 ClosedByInterruptException)
if (blocker != null) {
blocker.interrupt(this);
}
}
if (blocker != null) {
blocker.postInterrupt();
}
}
}
// 静态方法,查"当前线程"并清除标志。连续调两次,第二次多半是 false
public static boolean interrupted() {
return currentThread().getAndClearInterrupt();
}
// 实例方法,只查不清。循环里响应中断用它:while (!isInterrupted()) {...}
public boolean isInterrupted() {
return interrupted;
}
boolean getAndClearInterrupt() {
boolean oldValue = interrupted;
if (oldValue) { // 只有确认看到 true 才清零。若无脑写 false,读和写之间新来的中断会被吞掉
interrupted = false;
clearInterruptEvent();
}
return oldValue;
}
// sleep:当前线程睡指定时长,不释放任何锁(和 wait 的本质区别)
public static void sleep(long millis) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
long nanos = MILLISECONDS.toNanos(millis);
sleepNanos(nanos);
}
private static void sleepNanos(long nanos) throws InterruptedException {
ThreadSleepEvent event = beforeSleep(nanos); // JFR 埋点
try {
if (currentThread() instanceof VirtualThread vthread) {
vthread.sleepNanos(nanos); // 虚拟线程:卸载出载体线程,不占内核线程干等
} else {
sleepNanos0(nanos); // 平台线程:native,内核级挂起
}
} finally {
afterSleep(event);
}
}
// join:等目标线程死。实现就是在目标 Thread 对象的 monitor 上 wait,线程终止时 VM 会在该对象上 notifyAll
public final void join(long millis) throws InterruptedException {
if (millis < 0)
throw new IllegalArgumentException("timeout value is negative");
if (this instanceof VirtualThread vthread) {
if (isAlive()) {
long nanos = MILLISECONDS.toNanos(millis);
vthread.joinNanos(nanos);
}
return;
}
synchronized (this) { // 锁的是目标线程对象 —— 所以业务代码别在 Thread 实例上 wait/notify,会跟 join 打架
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis - // 醒来必须重查 isAlive 并重算剩余时间:防虚假唤醒 + 防别人乱 notify
NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else {
while (isAlive()) { // millis=0 表示无限等,同样必须循环检查
wait(0);
}
}
}
}
}原理串讲
一次 new Thread(r).start() 的完整链路:所有 public 构造器最终汇入包私有的 Thread(ThreadGroup, String, int, Runnable, long),这一步只在 Java 堆上建对象——分配 tid、把 task/priority/daemon 塞进 FieldHolder(优先级和 daemon 从父线程抄),并在此刻拷贝父线程的 inheritableThreadLocals。
此时没有任何 OS 资源被创建,状态是 NEW。调 start() 后,先在 synchronized (this) 里检查 holder.threadStatus != 0 挡掉二次启动,然后进 native 的 start0():HotSpot 在这里创建内核线程、把 JavaThread 地址写进 eetop,新线程起来后回调 run(),run() 取出 holder.task 经 runWith 执行。
任务跑完(或抛出未捕获异常)后 VM 调私有的 exit() 清理 threadLocals 等引用,把 eetop 归零,并在这个 Thread 对象的 monitor 上 notifyAll——正在 join() 里 wait 的线程就是这样被叫醒的,醒来后 isAlive() 返回 false,循环退出。
为什么 start 和 run 要拆开?因为”创建执行流”和”要执行的逻辑”是两件事:run 只是普通的回调方法,并发能力完全来自 start0 向内核申请的新执行流。
直接调 thread.run() 语法完全合法,但只是当前线程的一次普通方法调用——这是最经典的一个坑,源码层面看得清清楚楚:run 里没有任何魔法,就是 holder.task.run()。
为什么 interrupt 设计成协作式?被废弃的 stop()(JDK 25 里已经直接抛 UnsupportedOperationException)是异步强杀,线程可能在持锁改共享数据改到一半时被杀,锁释放了但数据是坏的,全进程被污染。
所以 JDK 改成:interrupt() 只把 interrupted 这个 volatile 标志置 true,再通过 interrupt0() 通知 VM 把阻塞中的线程唤醒(sleep/wait/join 醒来后发现标志被置,清掉标志并抛 InterruptedException)。
收不收、什么时候停,决定权在目标线程自己手里,它总有机会把事务做完或回滚。
为什么 getAndClearInterrupt 要”先看再清”而不是直接写 false?因为读标志和清标志之间存在窗口:如果这个瞬间恰好又来一次 interrupt,无条件写 false 会把这次新中断吞掉。
只在确认读到 true 时才清零,丢失的最坏情况也只是”把刚到的中断算进本次返回值”,语义仍然正确——这是无锁编程里典型的 check-then-act 窗口处理。
为什么要有 FieldHolder 这层间接?虚拟线程的目标是百万级实例,而 group/stackSize/priority/daemon 这些字段只对平台线程有意义。
把它们抽进 FieldHolder 后,虚拟线程的 holder 直接为 null,每个实例省下一整块字段的内存;同时 VM 需要直接按偏移量访问这些字段(如 threadStatus),集中放一个对象里也方便 VM 侧定位。
设计取舍
- RUNNABLE 不区分”就绪”和”运行中”:JVM 拿不到也不想追内核调度器的实时状态,粗粒度换取跨平台一致。
- sleep 不释放锁,wait 释放锁:sleep 是”我歇会儿”,与同步无关;wait 是”条件不满足我让出来等通知”,必须放锁否则别人没法改条件。
interrupted()清标志、isInterrupted()不清:前者给”我自己处理完这次中断”用,后者给循环退出条件用。catch 到 InterruptedException 时标志已被清除,不想吞中断就要Thread.currentThread().interrupt()恢复。- join 基于目标线程对象的 wait/notifyAll 实现,所以官方明确建议:不要在 Thread 实例上做业务的 wait/notify。
- 优先级基本只是给 OS 的建议(还要被 group.getMaxPriority() 截断),主流平台上几乎不可依赖,真实工程里控制执行顺序靠 JUC 同步器而不是 setPriority。