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.taskrunWith 执行。
任务跑完(或抛出未捕获异常)后 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。

延伸阅读