new Thread()之后发生了什么?

new Thread()就创建了一个线程了? 这么描述不太对,准确地说,new Thread()只创建了一个Java对象,真正的线程还不存在。我们写了new Thread()后,到操作系统里创建一个真正可执行的线程,中间经历了好几步,每一步涉及不同的层级。

我们可以来追踪new Thread()之后的完整链路:Java层做了什么,JVM层做了什么,操作系统层又做了什么。然后再看sleep和interrupt这些操作,在这三层之间是如何传递的。

new Thread()只创建了一个Java对象

调用new Thread()时,会调用Thread类的一个私有构造方法。这个构造方法做的事情是:设置线程名、线程组、优先级、守护线程标记、目标Runnable、栈大小,以及分配一个线程ID。

几个关键字段的初始值如下:

  • threadStatus = 0,表示线程尚未启动
  • interrupted = false
  • target保存了你传入的Runnable对象

构造方法里没有调用任何native方法,没有创建C++对象,也没有请求操作系统分配线程。此时这个Thread实例和堆上的其他Java对象没有区别,isAlive()返回false,getState()返回NEW。

可以在Window 11环境使用java 17写一段代码验证验证一下:

Java 复制代码
Thread t = new Thread(() -> { }, "my-worker");
System.out.println("state=" + t.getState());
System.out.println("isAlive=" + t.isAlive());

输出:

text 复制代码
state=NEW
isAlive=false

从上面的输出结果,可以知道,线程还没有真正存在。

start()把Java对象变成真正的线程

调用start()之后,事情才开始发生。start()方法的逻辑分两步:

第一步,检查threadStatus是否为0,不为0说明已经启动过,抛出IllegalThreadStateException。这也是为什么一个Thread对象只能start一次。

第二步,调用start0(),这是一个native方法,进入JVM层。

从Java层进入JVM层后,对应的函数是JVM_StartThread。这个函数做了三件关键的事:

创建JavaThread C++对象。 JVM内部用C++类JavaThread来管理每个Java线程。JavaThread构造方法里调用了os::create_thread(),这个函数负责创建操作系统线程。

创建操作系统线程。 在Windows上,HotSpot的实现调用_beginthreadex()来创建一个真正的操作系统线程。这个线程创建时带有CREATE_SUSPENDED标志,处于挂起状态,还没有开始执行。同时JVM还创建了一个OSThread C++对象,用来保存操作系统线程的句柄和线程ID。

建立Java对象和JVM对象之间的关联。 prepare()方法把Java层的Thread对象和JVM层的JavaThread对象互相引用起来:Java对象里保存了指向C++ JavaThread的指针,JavaThread里也保存了指向Java Thread对象的引用。

此时操作系统线程虽然已经创建,但还在挂起状态。最后JVM调用Thread::start(),设置Java线程状态为RUNNABLE,然后通过Windows的ResumeThread()恢复操作系统线程的执行。

操作系统线程被唤醒后,最终会调用Java层Thread对象的run()方法。你的业务代码从这里开始执行。

整个过程可以概括为一条调用链:

text 复制代码
Thread.start()
  → start0() (native)
    → JVM_StartThread()
      → new JavaThread()           // JVM层C++对象
        → os::create_thread()       // 创建OS线程(挂起状态)
          → _beginthreadex()        // Windows API
      → prepare()                   // 关联Java对象和JVM对象
      → Thread::start()
        → os::start_thread()        // 恢复OS线程执行
          → ResumeThread()          // Windows API

四层结构的对应关系

到这里可以看清整个结构了。一个正在运行的Java线程,实际上由四层对象组成:

层级 对象 说明
Java层 java.lang.Thread 你在代码里操作的Thread对象
JVM层 JavaThread (C++) JVM内部管理的线程对象
JVM层 OSThread (C++) 保存操作系统线程的句柄和ID
操作系统层 OS Thread 操作系统调度的执行单元

这四个对象是一一对应的关系。你在Java层对一个Thread对象调用方法,JVM层通过JavaThread找到对应的OSThread,再操作操作系统线程。反过来,操作系统线程的状态变化,也会通过OSThread和JavaThread反映到Java层的Thread.getState()上。

sleep在各层做了什么

Thread.sleep()是一个native方法,从Java层直接进入JVM层的JVM_Sleep函数。

JVM_Sleep做了这些事:

先检查中断标志。如果线程已经被标记为interrupted,直接抛出InterruptedException,根本不会进入睡眠。

然后把OSThread的状态设为SLEEPING。这个状态变化会反映到Java层的getState()上,返回值变为TIMED_WAITING。

最后调用ParkEvent.park(millis)。ParkEvent是JVM内部的同步原语,park操作会让操作系统线程进入等待状态。在Windows上,底层最终调用的是类似WaitForSingleObject()这样的系统调用。线程在操作系统层面被标记为等待状态,不再被调度器分配CPU时间片。

当睡眠时间到达或者被中断,park返回,OSThread状态恢复,线程重新进入可运行状态。

interrupt如何唤醒一个正在sleep的线程

调用thread.interrupt()时,Java层先把Thread对象的interrupted字段设为true,然后调用native方法interrupt0()

interrupt0()进入JVM层的JVM_Interrupt,最终调用JavaThread::interrupt()。这个方法同时做了四件事:

C++ 复制代码
void JavaThread::interrupt() {
    // 设置操作系统级别的中断标志
    osthread()->set_interrupted(true);
    // 唤醒正在sleep的线程
    _SleepEvent->unpark();
    // 唤醒LockSupport.park()阻塞的线程
    parker()->unpark();
    // 唤醒ObjectMonitor.wait()阻塞的线程
    _ParkEvent->unpark();
}

关键在于_SleepEvent->unpark()。正在sleep的线程在ParkEvent上等待,unpark操作会把这个等待唤醒。线程从park返回后,在JavaThread::sleep()的循环里检查到is_interrupted(true)返回true,于是返回false给JVM_Sleep。JVM_Sleep收到false后抛出InterruptedException。

这就是为什么在catch到InterruptedException后,isInterrupted()返回false:中断标志在检查时已经被清除了。

用代码验证

Java 复制代码
public class ThreadLifecycle {

    public static void main(String[] args) throws Exception {

        // step 1: new Thread() - only a Java object, no real thread yet
        Thread t = new Thread(() -> {
            try {
                System.out.println("  [worker] started, state=" + Thread.currentThread().getState());
                System.out.println("  [worker] thread id=" + Thread.currentThread().getId());
                System.out.println("  [worker] sleeping 5 seconds...");
                Thread.sleep(5000);
                System.out.println("  [worker] woke up normally");
            } catch (InterruptedException e) {
                System.out.println("  [worker] interrupted during sleep!");
                System.out.println("  [worker] isInterrupted=" + Thread.currentThread().isInterrupted());
            }
        }, "my-worker");

        System.out.println("[main] after new Thread():");
        System.out.println("  state=" + t.getState());
        System.out.println("  isAlive=" + t.isAlive());

        // step 2: start() - now JVM creates the real thread
        System.out.println("[main] calling start()...");
        t.start();
        // give the worker thread time to print its first lines
        Thread.sleep(200);

        System.out.println("[main] after start():");
        System.out.println("  state=" + t.getState());
        System.out.println("  isAlive=" + t.isAlive());

        // step 3: wait a moment, worker is sleeping
        Thread.sleep(500);
        System.out.println("[main] worker is now sleeping, state=" + t.getState());

        // step 4: interrupt the sleeping worker
        System.out.println("[main] calling interrupt()...");
        t.interrupt();

        t.join();
        System.out.println("[main] worker finished, state=" + t.getState());
    }
}

文件名ThreadLifecycle.java,用java ThreadLifecycle.java直接运行。

运行两次,输出结果是一致的:

text 复制代码
run 1:
[main] after new Thread():
  state=NEW
  isAlive=false
[main] calling start()...
  [worker] started, state=RUNNABLE
  [worker] thread id=23
  [worker] sleeping 5 seconds...
[main] after start():
  state=TIMED_WAITING
  isAlive=true
[main] worker is now sleeping, state=TIMED_WAITING
[main] calling interrupt()...
  [worker] interrupted during sleep!
  [worker] isInterrupted=false
[main] worker finished, state=TERMINATED

run 2:
[main] after new Thread():
  state=NEW
  isAlive=false
[main] calling start()...
  [worker] started, state=RUNNABLE
  [worker] thread id=23
  [worker] sleeping 5 seconds...
[main] after start():
  state=TIMED_WAITING
  isAlive=true
[main] worker is now sleeping, state=TIMED_WAITING
[main] calling interrupt()...
  [worker] interrupted during sleep!
  [worker] isInterrupted=false
[main] worker finished, state=TERMINATED

从输出可以看到几个关键的事实:

new Thread()之后state是NEW,isAlive是false。这时候还没有真正的线程,只是一个Java对象。

start()之后worker线程开始执行,state变成RUNNABLE。sleep之后变成TIMED_WAITING,这说明Java层的线程状态确实映射到了JVM和操作系统层。

interrupt()之后worker立刻从sleep中醒来(不需要等5秒),捕获InterruptedException,而且此时isInterrupted()返回false,因为中断标志在检查时已经被清除了。

最后线程执行完毕,state变成TERMINATED。

小结

Java的线程模型在JDK17中采用的是1:1模型:一个Java Thread对象对应JVM中一个JavaThread C++对象,再对应操作系统中一个原生线程。new Thread()只是创建了这个映射关系的起点,真正的线程要等start()之后才存在。

注意:JDK21正式发布,带来了虚拟线程。虚拟线程采用M:N模型,多个虚拟线程可以复用少量操作系统线程。这和JDK17的1:1模型有本质区别,是另一个话题了。

参考内容

java 17 openjdk

相关推荐
晚安日记wanna1 小时前
Vue3 script setup 的四层追问答到第三层才算过关
前端·vue.js·面试
亦暖筑序1 小时前
AgentScope Java 实战:Agent 的状态存在哪、怎么恢复、怎么隔离?
人工智能·后端·agent
斯维赤1 小时前
Spring AI | Function Calling 是什么?
java·后端
devpotato1 小时前
限流、熔断、降级:三者核心区别一文讲清
java
不灭的黄金瞳1231 小时前
Java数据类型与变量
java·开发语言·intellij-idea
yunwei371 小时前
eBPF 教程:BPF 调度器入门
linux·后端·性能优化
shehuiyuelaiyuehao1 小时前
算法47,分治快排,第K大
java
程序猫.1 小时前
算法刷题笔记:模拟题从入门到实战(含 LeetCode 例题与习题)
java·数据结构·算法