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)

什么时候需要切换到内核态?


  1. 线程生命周期相关
操作 是否切换 原因
new Thread() ❌ 不切换 纯 Java 对象创建
t.start() ✅ 切换 需要调用 pthread_create() 系统调用
t.join() ✅ 切换 需要内核等待线程结束
t.sleep() ✅ 切换 需要内核计时器
t.wait() ✅ 切换 需要内核监控等待队列
t.notify() ✅ 切换 需要内核唤醒等待队列

  1. I/O 操作
// 以下操作都需要切换到内核态
socket.read();      // 网络 I/O
fileInput.read();   // 文件 I/O
Database.query();   // JDBC 底层是网络 I/O

用户态:  Java 调用 JDBC 驱动
内核态:  系统调用 read()
等待 I/O 完成(线程阻塞)

  1. 同步操作
// 以下操作可能切换
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+

Reference