星云点击:星空遥控器
120.47M · 2026-02-04
Java线程的实现是一个经典的“跨层协作”问题。要深入理解它,我们需要将视角分为两层:用户层面的 JVM 和 系统层面的操作系统(OS)。
简单来说,JVM 规范只规定了线程应该具备的行为,而具体的实现方式(1:1、N:1 还是 N:M)则由具体的 JVM 实现决定。目前最主流的 HotSpot JVM 在传统的 Java 版本(Java 19 之前)中,采用的是 1:1 模型,即 Java 线程直接映射为操作系统的内核级线程。
在理解 JVM 之前,先看 OS 提供了什么。现代操作系统(Linux, Windows, macOS)将线程通常划分为如下两类:
本文介绍的线程主要基于Linux系统,因此这里先简单介绍一下Linux中的线程怎么实现:
在 Linux 中可以认为使用进程实现了线程 ,这涉及到 Linux 设计的哲学:不区分进程与线程,一切皆是“任务”。Linux 内核用同一个结构体 struct task_struct 来描述一个可调度的执行实体。无论是我们传统意义上的“进程”,还是“线程”,在内核看来都是一个 task_struct 实例。在Linux系统中Linux线程KLT(Kernel Level Thread)又被称为轻量级进程LWP(Light Weight Process)。
这里面的关键魔法在于 clone(): 创建新的执行实体,无论是进程还是线程,最终都调用 clone() 系统调用。其关键区别在于创建时传递的“资源共享标志位”:
fork() -> 最终调用 clone(),不指定共享标志。结果是:新 task_struct 拥有独立的内存地址空间、文件描述符表、信号处理等。pthread_create() -> 最终调用 clone(),指定共享标志(如 CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND...)。结果是:新 task_struct 共享父任务的内存地址空间、文件系统信息、打开的文件、信号处理程序等。操作系统中的线程模型,主要围绕内核线程和用户线程之间的关系展开,细节可见下面章节。
对应原理:每个用户线程调用一次 clone,创建一个独立的LWP。它们不共享地址空间映射关系(各自独立看待),完全由内核调度。
由操作系统内核直接管理。
线程的创建、销毁、调度都由 OS 完成(需要切换到内核态)。
每个线程都有独立的内核栈和 PCB(在 Linux 中实际由 task_struct 描述)。
优点:可以利用多核 CPU,一个线程阻塞了,其他线程还可以运行。
缺点:上下文切换开销大(User Space <-> Kernel Space),线程创建较重。
对应原理:只调用了一次 clone 创建一个 LWP。所有的用户线程都在用户空间模拟,内核只“看”到这唯一的一个 LWP。
对应原理:调用了 N 次 clone 创建 N 个 LWP 组成 LWP 组。M 个用户线程动态绑定到这些 LWP 上。结合了 LWP 的并行能力和用户态线程的轻量。
现状: HotSpot JVM (Java 1.2 - Java 18 默认模式)
图解特点:
classDiagram
%% 定义 JVM 用户态层
class JavaThread {
+run()
+start()
}
%% 定义 OS 内核态层
class OSThread {
+task_struct
+Kernel Stack
}
%% 定义 CPU 硬件层
class CPU {
+Core 1
+Core 2
}
%% 映射关系 (1:1)
JavaThread "1" --> "1" OSThread : 直接映射
%% 调度关系
OSThread --> CPU : 由OS调度器分配
note for JavaThread "JVM 层面 (用户态)"
note for OSThread "OS 层面 (内核态)<br>如:Linux LWP"
文字解析:
JavaThread 在堆中被实例化。start() 时,JVM 调用底层 API(如 pthread_create)创建一个 OSThread。JavaThread 的所有操作都由这个唯一的 OSThread 执行。如果 JavaThread 阻塞,OSThread 也会阻塞,被 OS 挂起。历史: JVM 早期“绿色线程”
图解特点:
classDiagram
class JVM_Scheduler {
+用户态调度器
+run_queue
}
class JavaThread {
+run()
}
class OSThread {
+task_struct
}
class CPU {
+Core 1
}
%% JVM 中的多个线程
JavaThread "N" ..> "1" JVM_Scheduler : 注册到调度器
%% JVM 调度器绑定唯一 OS 线程
JVM_Scheduler --> "1" OSThread : 绑定
%% OS 调度
OSThread --> CPU
note for JVM_Scheduler "JVM 自己管理切换<br>(无需进入内核态)"
note for JavaThread "用户级线程<br>(绿色线程)"
文字解析:
JVM_Scheduler)。JavaThread A, B, C 都在用户空间,不涉及内核创建。OSThread。JavaThread A 发起了 I/O 阻塞,底层的 OSThread 就会卡住,导致 JavaThread B 和 C 即使就绪也无法运行(因为 OS 挂起了这唯一的线程)。未来: Java 21+ 虚拟线程 / Go Goroutine
图解特点:
classDiagram
class JVM_Scheduler {
+用户态调度器
+任务队列
}
class VirtualThread {
+run()
}
class CarrierThread {
+OS Thread (LWP)
}
class CPU {
+Multi-Core
}
%% M 个虚拟线程注册到 JVM 调度器
VirtualThread "M" ..> "1" JVM_Scheduler : 调度
%% N 个载体线程 (OS 线程)
JVM_Scheduler "1" --> "N" CarrierThread : 动态映射
%% OS 调度载体线程
CarrierThread --> CPU : 并行执行
note for VirtualThread "轻量级用户线程(Virtual Thread)"
note for CarrierThread "内核线程<br>数量固定"
note for JVM_Scheduler "M:N 关键<br>处理阻塞与挂载"
文字解析:
VirtualThread(虚拟线程)非常轻量,可以创建成千上万个。JVM_Scheduler 负责将虚拟线程“Mount”(挂载)到 CarrierThread(载体线程/OS线程)上执行。VirtualThread 遇到阻塞(如读网络)时,JVM 会将其卸载,CarrierThread 立刻空闲下来去执行其他的 VirtualThread。最后再给出一个Java运行线程的示例分析:
Thread t = new Thread(() -> {
// 这段代码会在一个真正的内核线程上运行
System.out.println(Thread.currentThread().getName());
});
t.start(); // 这里发生了从Java到OS的“协议转换”
启动一个Java线程涉及:
pthread_create、绑定到JVM内部数据结构