Java 平台线程与虚拟线程
Java 默认线程
java 默认创建的线程就是 平台线程,平台线程与 OS线程是1:1的关系。
- 我创建多少平台线程就会创建多少OS线程
- 我创建的线程是通过 Java 锁提供的抽象接口实现的,不需要关心底层的具体实现
- 从用户态切换到内核态需要耗费资源和时间(用户态 → 内核态切换开销:10-100 μs)
创建线程时发生了什么
Thread t = new Thread(() -> {
System.out.println("运行中");
});
t.start();
底层流程:
1. Java 虚拟机 (JVM)
│
├── 申请内存:线程栈 (~1MB)
│
├── 调用 OS API
│ └── Linux: pthread_create()
│ └── Windows: CreateThread()
│
└── OS 内核
├── 创建内核级线程 (KSE - Kernel Scheduling Entity)
├── 分配 CPU 时间片
└── 返回线程句柄 (Thread Handle)
什么时候需要切换到内核态?
- 线程生命周期相关
| 操作 | 是否切换 | 原因 |
|---|---|---|
new Thread() |
❌ 不切换 | 纯 Java 对象创建 |
t.start() |
✅ 切换 | 需要调用 pthread_create() 系统调用 |
t.join() |
✅ 切换 | 需要内核等待线程结束 |
t.sleep() |
✅ 切换 | 需要内核计时器 |
t.wait() |
✅ 切换 | 需要内核监控等待队列 |
t.notify() |
✅ 切换 | 需要内核唤醒等待队列 |
- I/O 操作
// 以下操作都需要切换到内核态
socket.read(); // 网络 I/O
fileInput.read(); // 文件 I/O
Database.query(); // JDBC 底层是网络 I/O
用户态: Java 调用 JDBC 驱动
│
▼
内核态: 系统调用 read()
│
▼
等待 I/O 完成(线程阻塞)
- 同步操作
// 以下操作可能切换
synchronized(obj) { // 轻量级CAS在用户态,但阻塞时需要内核态
// ...
}
Lock.lock(); // ReentrantLock 内部使用 AQS
需要内核介入的操作(创建线程、阻塞等待、I/O、锁竞争)会切换到内核态,纯用户态代码执行不切换。
Java 虚拟线程
Java 虚拟线程与默认线程不同,它采用 M:N 调度模型(M 个虚拟线程复用 N 个平台线程)。
// 虚拟线程
Thread virtualThread = Thread.ofVirtual().start(() -> {
// 虚拟线程,不会直接创建 OS 线程
});
// 或使用 ExecutorService
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
工作原理:
虚拟线程1 ──┐
虚拟线程2 ──┼─→ Carrier Thread 1 (平台线程 / OS 线程)
虚拟线程3 ──┘ ↓
调度器 (ForkJoinPool)
↓
虚拟线程4 ──┐ ↑
虚拟线程5 ──┼─→ Carrier Thread 2 (平台线程 / OS 线程)
虚拟线程6 ──┘

核心特点:
- 轻量级:创建成本极低 (~1KB 栈空间)
- 高并发:支持百万级虚拟线程
- 非阻塞:I/O 阻塞时挂起虚拟线程,不阻塞载体线程
平台线程 vs 虚拟线程对比
| 特性 | 平台线程 | 虚拟线程 |
|---|---|---|
| 映射关系 | 1:1 (OS 线程) | M:N (复用少量平台线程) |
| 内存占用 | ~1MB/线程 | ~1KB/线程 |
| 创建成本 | 高 (~1ms) | 极低 (~μs) |
| 并发上限 | 千级 | 百万级 |
| 阻塞行为 | 阻塞 OS 线程 | 只挂起虚拟线程 |
| 适用场景 | CPU 密集型 | I/O 密集型 |
| 线程池 | 需要 | 不需要 |
| JDK 版本 | Java 1.0+ | Java 21+ |