【Java多线程与高并发系列】第2讲:核心概念扫盲:进程 vs. 线程

106 阅读5分钟

🚀 系列导读: 在上一讲中,我们探讨了为什么多线程能让程序“飞起来”。 本讲我们继续深入底层,从 进程与线程的区别 讲起,一路探索到 Thread 源码实现机制,再到 Java 新时代的虚拟线程(Virtual Thread) 。 这是一讲打通“从概念到源码再到未来”的关键课程。


💡 一、进程 vs. 线程:操作系统视角理解

我们首先明确两个概念:

  • 进程(Process) :系统资源(内存、文件、句柄)的基本分配单位。
  • 线程(Thread) :CPU 调度和执行的最小单位。

🧠 举个例子:

当你启动 IDEA(或任何 Java 程序)时:

  • 操作系统会创建一个独立的“进程”,分配独立的内存空间;

  • 在这个进程内部,Java 虚拟机会再创建多个线程,比如:

    • 主线程(main)
    • GC线程
    • JIT编译线程
    • Finalizer线程 等等。

可以理解为👇

“进程是容器,线程是执行者。”


🧩 二、Java 中线程的本质:系统线程 vs 虚拟线程

很多人以为 new Thread() 就是 Java 自己实现的“线程”,但实际上:

Java 中的线程是由操作系统内核支持的“系统级线程”。


⚙️ 1️⃣ 创建线程的系统过程

当你在 Java 中执行:

new Thread(() -> System.out.println("Hello")).start();

底层流程如下(以 HotSpot JVM 为例):

  1. Java 层调用 Thread.start()
  2. start() 方法是一个 native 方法,最终调用到 JVM 的 JVM_StartThread()
  3. JVM_StartThread() 内部再调用 pthread_create()(Linux)CreateThread()(Windows)
  4. 操作系统为该线程分配 内核线程(Kernel Thread)
  5. 操作系统的调度器(Scheduler) 决定线程的实际执行顺序。

📘 简单来说:

每一个 Java 线程都映射到一个 系统级线程。 操作系统负责线程调度,而非 JVM。


🧩 2️⃣ Thread 源码深度解析

我们看一下 JDK 源码片段(以 JDK 17 为例):

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)优先级调度

这也是为什么:

Thread t1 = new Thread(...);
Thread t2 = new Thread(...);
t1.start();
t2.start();

两者执行顺序 不可预测


⚙️ 三、Java 创建线程的四种方式(回顾 + 扩展)

方式实现特点
① 继承 Thread重写run()方法简单直观,但不推荐
② 实现 Runnable实现任务与线程分离更灵活,可被多个线程共享
③ 实现 Callable + FutureTask可返回结果与抛异常适合异步计算
④ 使用线程池 ExecutorService线程复用、性能最佳企业级开发首选

🌟 小贴士:

在实际项目中,推荐使用 线程池(ExecutorService)

  • 避免频繁创建系统线程;
  • 控制最大并发数;
  • 支持任务排队、拒绝策略等。

💻 四、实战:最简多线程示例

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());
    }
}

可能输出:

主线程:main
子线程:Thread-0

⚠️ 执行顺序不确定,这正是多线程并发执行的体现。


🌙 五、扩展:守护线程 (Daemon Thread)

在 Java 中,线程分为两类:

类型说明示例
用户线程程序的主要执行逻辑主线程、业务线程
守护线程服务于用户线程GC线程、后台监控线程

当所有用户线程执行完毕后,JVM 会自动退出,即使守护线程仍在运行。

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 示例)

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10).forEach(i -> 
        executor.submit(() -> {
            System.out.println("任务 " + i + " 在线程:" + Thread.currentThread());
            Thread.sleep(1000);
            return i;
        })
    );
}

输出(部分):

任务 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 的完整生命周期。