new Thread()就创建了一个线程了? 这么描述不太对,准确地说,new Thread()只创建了一个Java对象,真正的线程还不存在。我们写了new Thread()后,到操作系统里创建一个真正可执行的线程,中间经历了好几步,每一步涉及不同的层级。
我们可以来追踪new Thread()之后的完整链路:Java层做了什么,JVM层做了什么,操作系统层又做了什么。然后再看sleep和interrupt这些操作,在这三层之间是如何传递的。
new Thread()只创建了一个Java对象
调用new Thread()时,会调用Thread类的一个私有构造方法。这个构造方法做的事情是:设置线程名、线程组、优先级、守护线程标记、目标Runnable、栈大小,以及分配一个线程ID。
几个关键字段的初始值如下:
threadStatus = 0,表示线程尚未启动interrupted = falsetarget保存了你传入的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