Skip to content

进程 vs 线程 vs 协程

先搞懂这三个「执行单位」,后面的内容才不迷糊。

一个故事

想象你在运营一家餐厅:

进程 = 餐厅本身(独立的资源:厨房、餐桌、人员)
线程 = 餐厅里的员工(共享厨房和食材,但各忙各的)
协程 = 员工里的「高效多面手」(不需要经理协调,自己切换任务)

进程是资源分配的最小单位,线程是 CPU 调度的最小单位,协程是更轻量的协作式任务单元。

进程(Process)

进程是程序的一次执行,是操作系统分配资源的基本单位。

当你双击打开一个 Java 程序,操作系统就会创建一个进程。这个进程有自己独立的:

  • 内存空间(代码段、数据段、堆、栈)
  • 文件句柄(打开的文件、网络连接)
  • 进程 ID(PID)
┌─────────────────────────────────────────────────────────┐
│                      进程                                │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │  进程控制块 (PCB)                                  │   │
│  │  PID │ 状态 │ 程序计数器 │ 寄存器 │ 调度信息 │    │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │  进程内存空间                                      │   │
│  │  代码段 │ 数据段 │ BSS段 │ 堆 │ 栈 │ 环境变量 │    │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  文件句柄 / 网络连接 / 其他系统资源                      │
│                                                         │
└─────────────────────────────────────────────────────────┘

Java 中获取进程信息

java
public class ProcessDemo {

    public static void main(String[] args) {
        // 获取当前进程
        long pid = ProcessHandle.current().pid();
        System.out.println("当前进程 ID: " + pid);

        // 获取当前进程信息
        ProcessHandle.current().info().forEach(info ->
            System.out.println(info.toString())
        );

        // Java 19+ 可以获取子进程
        // ProcessHandle.current().children().forEach(...);
    }
}

进程的特点

  • 独立运行,一个进程崩溃不会影响其他进程
  • 创建和销毁开销大(需要分配独立内存空间)
  • 进程间通信(IPC)需要额外机制:管道、消息队列、Socket

线程(Thread)

线程是进程内的执行单元,是 CPU 调度的最小单位。

一个进程可以包含多个线程,所有线程共享进程的内存空间(堆、方法区),但每个线程有自己独立的栈。

┌─────────────────────────────────────────────────────────┐
│                      进程                                │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  线程1           线程2           线程3           线程4  │
│  ┌────────┐      ┌────────┐      ┌────────┐      ┌────────┐ │
│  │ PC, SP │      │ PC, SP │      │ PC, SP │      │ PC, SP │ │
│  │ 寄存器  │      │ 寄存器  │      │ 寄存器  │      │ 寄存器  │ │
│  │ 栈     │      │ 栈     │      │ 栈     │      │ 栈     │ │
│  └────────┘      └────────┘      └────────┘      └────────┘ │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │              共享区域                             │   │
│  │  代码段 │ 数据段 │ 堆 │ 文件句柄 │ 网络连接 │     │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

PC = 程序计数器(下一条指令),SP = 栈指针(当前栈帧)

Java 中操作线程

java
public class ThreadDemo {

    public static void main(String[] args) {
        // 获取主线程
        Thread mainThread = Thread.currentThread();
        System.out.println("主线程: " + mainThread.getName());
        System.out.println("主线程 ID: " + mainThread.getId());

        // 创建新线程
        Thread worker = new Thread(() -> {
            System.out.println("工作线程: " + Thread.currentThread().getName());
            System.out.println("工作线程 ID: " + Thread.currentThread().getId());
        }, "Worker");

        worker.start();

        // 线程状态
        System.out.println("启动前状态: " + worker.getState());
        System.out.println("是否存活: " + worker.isAlive());
    }
}

线程的特点

  • 共享进程资源(堆内存、文件句柄),通信方便
  • 创建和销毁开销小
  • 一个线程崩溃,可能导致整个进程崩溃
  • 需要同步机制(因为共享数据)

协程(Coroutine)

协程是比线程更轻量的执行单元,是一种协作式的任务调度方式。

为什么需要协程?

线程虽小,但也有开销:

  • 创建和切换需要内核态操作
  • 每个线程默认占用 1MB 栈空间
  • 线程太多,上下文切换成本高

协程的特点:

  • 用户态切换,不需要内核操作
  • 栈空间可以很小(几 KB)
  • 协作式调度:主动让出 CPU,不抢占
线程:抢占式调度(操作系统决定谁运行)
协程:协作式调度(自己决定何时让出)

Java 中的协程

Java 21 引入了虚拟线程(Virtual Threads),这是 JDK 层面的协程实现:

java
// 传统线程
Thread thread = Thread.ofPlatform().name("platform").start(() -> {
    // ...
});

// 虚拟线程(协程)
Thread virtualThread = Thread.ofVirtual().name("virtual").start(() -> {
    // ...
});

// 使用 Thread.startVirtualThread() 更简洁
Thread.startVirtualThread(() -> {
    System.out.println("虚拟线程运行中");
});

虚拟线程 vs 传统线程

对比项传统线程虚拟线程
创建开销大(1MB 栈空间)小(几百字节)
切换成本内核态,高用户态,低
数量数千个就很多了可以轻松创建数百万个
调度操作系统抢占式JDK 协作式

协程适用场景

java
public class CoroutineDemo {

    public static void main(String[] args) throws InterruptedException {
        // 场景:处理大量 I/O 任务
        // 传统方式需要创建很多线程,开销大
        // 虚拟线程可以轻松创建数百万个

        long start = System.nanoTime();

        // 创建 10 万个虚拟线程
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 100_000).forEach(i ->
                executor.submit(() -> {
                    // 模拟 I/O 操作
                    try {
                        Thread.sleep(Duration.ofSeconds(1));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return i;
                })
            );
        }

        long duration = (System.nanoTime() - start) / 1_000_000;
        System.out.println("10 万个任务耗时: " + duration + "ms");
    }
}

三者对比

对比项进程线程协程(虚拟线程)
定义程序执行实例进程内的执行单元更轻量的执行单元
资源分配操作系统分配共享进程资源共享线程资源
创建开销极小
切换开销极小
通信IPC(管道/消息队列)直接共享内存直接共享内存
隔离性高(独立地址空间)低(共享地址空间)低(共享地址空间)
调度方式操作系统抢占式操作系统抢占式协作式

实战:理解 Java 程序

java
public class JavaProcessDemo {

    public static void main(String[] args) {
        // 当你运行这个程序:
        // 1. 操作系统创建一个进程
        // 2. JVM 启动一个主线程
        // 3. main() 在主线程中运行

        System.out.println("进程 ID: " + ProcessHandle.current().pid());
        System.out.println("主线程: " + Thread.currentThread().getName());
        System.out.println("主线程 ID: " + Thread.currentThread().getId());

        // 创建新线程
        new Thread(() -> {
            System.out.println("新线程: " + Thread.currentThread().getName());
            System.out.println("新线程 ID: " + Thread.currentThread().getId());
        }, "MyThread").start();

        // 虚拟线程(Java 21+)
        Thread.startVirtualThread(() ->
            System.out.println("虚拟线程: " + Thread.currentThread().getName())
        );
    }
}

运行结果示例

进程 ID: 12345
主线程: main
主线程 ID: 1
新线程: MyThread
新线程 ID: 20
虚拟线程: VirtualThread-1

总结

  • 进程是资源分配的单位,独立运行,一个崩溃不影响另一个
  • 线程是 CPU 调度的单位,共享进程资源,切换开销小
  • 协程是比线程更轻量的执行单元,用户态切换,适合大量 I/O 任务
  • Java 21+ 的虚拟线程让协程编程变得简单

记住:进程是「房子」,线程是「住在房子里的人」,协程是「人的超能力——不需要经理分配,自己决定何时切换任务」。

下一节我们看线程的创建方式,以及哪种方式更好。

基于 VitePress 构建