🚀 系列导读: 在上一讲中,我们探讨了为什么多线程能让程序"飞起来"。 本讲我们继续深入底层,从 进程与线程的区别 讲起,一路探索到 Thread 源码实现机制 ,再到 Java 新时代的虚拟线程(Virtual Thread) 。 这是一讲打通"从概念到源码再到未来"的关键课程。
💡 一、进程 vs. 线程:操作系统视角理解
我们首先明确两个概念:
- 进程(Process) :系统资源(内存、文件、句柄)的基本分配单位。
- 线程(Thread) :CPU 调度和执行的最小单位。
🧠 举个例子:
当你启动 IDEA(或任何 Java 程序)时:
-
操作系统会创建一个独立的"进程",分配独立的内存空间;
-
在这个进程内部,Java 虚拟机会再创建多个线程,比如:
- 主线程(main)
- GC线程
- JIT编译线程
- Finalizer线程 等等。
可以理解为👇
"进程是容器,线程是执行者。"
🧩 二、Java 中线程的本质:系统线程 vs 虚拟线程
很多人以为 new Thread()
就是 Java 自己实现的"线程",但实际上:
Java 中的线程是由操作系统内核支持的"系统级线程"。
⚙️ 1️⃣ 创建线程的系统过程
当你在 Java 中执行:
java
new Thread(() -> System.out.println("Hello")).start();
底层流程如下(以 HotSpot JVM 为例):
- Java 层调用
Thread.start()
; start()
方法是一个 native 方法 ,最终调用到 JVM 的JVM_StartThread()
;JVM_StartThread()
内部再调用 pthread_create()(Linux) 或 CreateThread()(Windows) ;- 操作系统为该线程分配 内核线程(Kernel Thread) ;
- 由 操作系统的调度器(Scheduler) 决定线程的实际执行顺序。
📘 简单来说:
每一个 Java 线程都映射到一个 系统级线程。 操作系统负责线程调度,而非 JVM。
🧩 2️⃣ Thread 源码深度解析
我们看一下 JDK 源码片段(以 JDK 17 为例):
java
public class Thread implements Runnable {
private Runnable target;
public synchronized void start() {
if (started)
throw new IllegalThreadStateException();
started = true;
start0(); // native 方法
}
private native void start0();
}
💡 start0()
是一个 JNI 本地方法,最终由 JVM 调用系统的底层线程 API 来完成线程创建。
🔍 3️⃣ 调度机制:Java 不负责调度!
Java 不负责决定哪个线程先执行。 线程的调度完全由操作系统控制,通常使用 时间片轮转(Time Slice Round-Robin) 或 优先级调度。
这也是为什么:
java
Thread t1 = new Thread(...);
Thread t2 = new Thread(...);
t1.start();
t2.start();
两者执行顺序 不可预测。
⚙️ 三、Java 创建线程的四种方式(回顾 + 扩展)
方式 | 实现 | 特点 |
---|---|---|
① 继承 Thread | 重写run() 方法 |
简单直观,但不推荐 |
② 实现 Runnable | 实现任务与线程分离 | 更灵活,可被多个线程共享 |
③ 实现 Callable + FutureTask | 可返回结果与抛异常 | 适合异步计算 |
④ 使用线程池 ExecutorService | 线程复用、性能最佳 | 企业级开发首选 |
🌟 小贴士:
在实际项目中,推荐使用 线程池(ExecutorService) :
- 避免频繁创建系统线程;
- 控制最大并发数;
- 支持任务排队、拒绝策略等。
💻 四、实战:最简多线程示例
java
public class HelloThread {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("子线程:" + Thread.currentThread().getName());
});
t.start();
System.out.println("主线程:" + Thread.currentThread().getName());
}
}
可能输出:
css
主线程:main
子线程:Thread-0
⚠️ 执行顺序不确定,这正是多线程并发执行的体现。
🌙 五、扩展:守护线程 (Daemon Thread)
在 Java 中,线程分为两类:
类型 | 说明 | 示例 |
---|---|---|
用户线程 | 程序的主要执行逻辑 | 主线程、业务线程 |
守护线程 | 服务于用户线程 | GC线程、后台监控线程 |
当所有用户线程执行完毕后,JVM 会自动退出,即使守护线程仍在运行。
java
public class DaemonExample {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
System.out.println("守护线程运行中...");
try { Thread.sleep(300); } catch (Exception ignored) {}
}
});
t.setDaemon(true);
t.start();
Thread.sleep(1000);
System.out.println("主线程结束");
}
}
🧬 六、Java 新时代:虚拟线程(Virtual Thread)
⚡ Java 19 引入的 Project Loom,正式带来了虚拟线程(Virtual Thread) 。
🌍 1️⃣ 虚拟线程是什么?
虚拟线程是一种 由 JVM 而非操作系统管理 的轻量级线程。
- 每个虚拟线程不再映射为一个系统级线程;
- 数百万个虚拟线程可共享少量平台线程;
- 调度由 JVM 自行管理,而非 OS;
- 大幅降低线程切换成本。
🧩 2️⃣ 使用方式(JDK 21 示例)
java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10).forEach(i ->
executor.submit(() -> {
System.out.println("任务 " + i + " 在线程:" + Thread.currentThread());
Thread.sleep(1000);
return i;
})
);
}
输出(部分):
ruby
任务 0 在线程:VirtualThread[#1]/runnable@ForkJoinPool
任务 1 在线程:VirtualThread[#2]/runnable@ForkJoinPool
...
💡 特点:
- 启动速度极快;
- 切换几乎无成本;
- IO 阻塞时自动挂起,不占系统线程。
🔍 3️⃣ 对比总结
特性 | 平台线程(传统) | 虚拟线程(Loom) |
---|---|---|
底层实现 | 操作系统内核线程 | JVM 管理 |
数量级 | 数千个 | 上百万个 |
切换成本 | 高(系统调用) | 低(用户态切换) |
适用场景 | 计算密集型任务 | IO 密集型任务 |
📘 虚拟线程不是取代平台线程,而是补充。 主线程、GC线程依然是平台线程,但业务层可大量使用虚拟线程。
📖 七、小结
知识点 | 核心理解 |
---|---|
进程 vs 线程 | 线程是进程的执行单元,共享同一内存 |
Java线程实现 | 每个 Java Thread 对应一个 OS 线程 |
调度机制 | Java 不负责调度,由操作系统完成 |
Thread源码 | start() 调用 nativestart0() 创建系统线程 |
守护线程 | 不会阻止 JVM 退出 |
虚拟线程 | JVM 自管理的轻量级线程,可达百万级并发 |
✅ 思考与预告
💭 思考题: 如果虚拟线程可以创建上百万个,它是否意味着我们再也不需要线程池?
🔜 下一讲预告: 第3讲:线程的生命周期与状态转换 ------ 揭开NEW → RUNNABLE → BLOCKED
的完整生命周期。