当前位置: 首页 > news >正文

进程、线程、协程、虚拟线程,傻傻分不清楚

前言

最近虚拟线程火了。

但有些小伙伴对进程、线程、协程、虚拟线程之间的区别和联系还是没有搞清楚。

今天这篇文章就跟大家一起聊聊,希望对你会有所帮助。

一、进程与线程

有些小伙伴在工作中可能经常听到"进程"和"线程"这两个词,但未必真正理解它们之间的本质区别。

让我用一个简单的比喻来解释:

想象一家大工厂(操作系统):

  • 进程就像工厂中的一个独立车间,每个车间有自己独立的空间、原料和工具。
  • 线程就像车间中的工人,共享车间的资源,协同完成生产任务。

进程:独立的执行环境

进程是操作系统进行资源分配和调度的基本单位

每个进程都有自己独立的地址空间、数据栈、代码段和其他系统资源。

// Java中创建进程的示例
public class ProcessExample {public static void main(String[] args) throws IOException {// 启动一个新的进程(比如打开计算器)ProcessBuilder processBuilder = new ProcessBuilder("calc.exe");Process process = processBuilder.start();System.out.println("进程ID: " + process.pid());System.out.println("是否存活: " + process.isAlive());// 等待进程结束try {int exitCode = process.waitFor();System.out.println("进程退出码: " + exitCode);} catch (InterruptedException e) {e.printStackTrace();}}
}

进程的特点

  1. 独立性:每个进程有独立的地址空间,互不干扰
  2. 安全性:一个进程崩溃不会影响其他进程
  3. 开销大:创建和销毁进程需要较大的系统开销
  4. 通信复杂:进程间通信(IPC)需要特殊的机制

线程:轻量级的执行单元

线程是进程内的执行单元,是CPU调度和执行的基本单位

一个进程可以包含多个线程,这些线程共享进程的资源。

// Java中创建线程的两种方式
public class ThreadExample {public static void main(String[] args) {// 方式1:继承Thread类Thread thread1 = new MyThread();thread1.start();// 方式2:实现Runnable接口Thread thread2 = new Thread(new MyRunnable());thread2.start();// 方式3:使用Lambda表达式Thread thread3 = new Thread(() -> {System.out.println("Lambda线程执行: " + Thread.currentThread().getName());});thread3.start();}
}class MyThread extends Thread {@Overridepublic void run() {System.out.println("MyThread执行: " + Thread.currentThread().getName());}
}class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("MyRunnable执行: " + Thread.currentThread().getName());}
}

线程的特点

  1. 共享资源:同一进程内的线程共享内存空间和系统资源
  2. 轻量级:创建和销毁线程的开销比进程小
  3. 通信简单:线程间可以直接读写共享数据
  4. 安全性问题:需要处理线程同步和资源共享问题

二、线程的深入剖析

要真正理解线程,我们需要深入操作系统层面。

现代操作系统通常采用三种线程模型:

1. 用户级线程(ULT)

用户级线程完全在用户空间实现,操作系统不知道它们的存在。线程的创建、调度、同步等都由用户级的线程库完成。

优点

  • 线程切换不需要陷入内核态,开销小
  • 调度算法可以由应用程序自定义
  • 不依赖于操作系统支持

缺点

  • 一个线程阻塞会导致整个进程阻塞
  • 无法利用多核CPU的优势

2. 内核级线程(KLT)

内核级线程由操作系统内核直接支持和管理。每个内核线程对应一个内核级的调度实体。

优点

  • 一个线程阻塞不会影响其他线程
  • 能够利用多核CPU并行执行

缺点

  • 线程切换需要陷入内核态,开销较大
  • 创建线程需要系统调用

3. 混合模型

现代操作系统通常采用混合模型,将用户级线程映射到内核级线程上。

Java线程就是这种模型的具体实现。

// Java线程与操作系统线程的对应关系
public class ThreadInfoExample {public static void main(String[] args) {// 创建多个线程for (int i = 0; i < 5; i++) {int threadId = i;new Thread(() -> {System.out.println("Java线程: " + Thread.currentThread().getName() +", 操作系统线程ID: " + getThreadId());try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}).start();}}// 获取操作系统线程ID(Java 10+)private static long getThreadId() {return Thread.currentThread().threadId();}
}

三、协程

有些小伙伴可能听说过协程(Coroutine),尤其是在Go语言中非常流行。

那么协程和线程有什么区别呢?

协程的本质

协程是一种比线程更加轻量级的执行单元,它由程序员在用户空间控制调度,而不是由操作系统内核调度。

// Java中可以使用第三方库实现协程(如Quasar)
// 以下是伪代码示例,展示协程的概念
public class CoroutineExample {public static void main(String[] args) {// 创建协程Coroutine coroutine1 = new Coroutine(() -> {System.out.println("协程1开始");Coroutine.yield(); // 主动让出执行权System.out.println("协程1继续");});Coroutine coroutine2 = new Coroutine(() -> {System.out.println("协程2开始");Coroutine.yield(); // 主动让出执行权System.out.println("协程2继续");});// 手动调度协程coroutine1.run();coroutine2.run();coroutine1.run();coroutine2.run();}
}

协程的特点

  1. 极轻量级:一个程序可以轻松创建数十万个协程
  2. 协作式调度:由程序员控制调度时机
  3. 低成本切换:切换不需要陷入内核态
  4. 同步编程风格:可以用同步的方式编写异步代码

协程 vs 线程

为了更清晰地理解协程和线程的区别。

我们先看看执行单元的对比图:

image

再看看创建数量的对比图:
image

四、虚拟线程

Java 19引入了虚拟线程(Virtual Threads),这是Java并发模型的一次重大革新。

虚拟线程旨在解决传统平台线程的局限性。

为什么需要虚拟线程?

有些小伙伴在工作中可能遇到过下面这些的问题。

为了处理大量并发请求,我们创建了大量线程,但很快遇到了瓶颈:

  1. 线程数量限制:操作系统线程数有限制(通常几千个)
  2. 内存开销大:每个线程都需要分配栈内存(默认1MB)
  3. 上下文切换开销:线程切换需要内核参与,开销较大

虚拟线程的实现原理

虚拟线程是JDK实现的轻量级线程,它们不是由操作系统直接调度,而是由JDK调度到平台线程(操作系统线程)上执行。

// Java 19+ 虚拟线程使用示例
public class VirtualThreadExample {public static void main(String[] args) throws InterruptedException {// 创建虚拟线程Thread virtualThread = Thread.ofVirtual().start(() -> {System.out.println("虚拟线程执行: " + Thread.currentThread());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});// 等待虚拟线程结束virtualThread.join();// 使用虚拟线程处理大量任务try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {for (int i = 0; i < 10_000; i++) {int taskId = i;executor.submit(() -> {System.out.println("任务 " + taskId + " 在线程: " + Thread.currentThread());Thread.sleep(1000);return taskId;});}}}
}

虚拟线程的优势

  1. 轻量级:可以创建数百万个虚拟线程而不会耗尽资源
  2. 低成本阻塞:虚拟线程阻塞时不会阻塞平台线程
  3. 简化并发编程:可以用同步的代码风格编写高并发程序
  4. 兼容现有代码:虚拟线程是Thread的实现,兼容现有API

五、虚拟线程如何工作?

为了真正理解虚拟线程,我们需要深入其工作原理。

虚拟线程的实现基于一个关键概念:continuation

Continuation的概念

Continuation表示一个可暂停和恢复的执行上下文。当虚拟线程执行阻塞操作时,JDK会挂起当前的continuation,并释放平台线程去执行其他任务。

// 伪代码:展示continuation的概念
public class ContinuationExample {public static void main(String[] args) {ContinuationScope scope = new ContinuationScope("example");Continuation continuation = new Continuation(scope, () -> {System.out.println("步骤1");Continuation.yield(scope); // 暂停执行System.out.println("步骤2");Continuation.yield(scope); // 暂停执行System.out.println("步骤3");});// 分步执行while (!continuation.isDone()) {System.out.println("开始执行步骤...");continuation.run();System.out.println("步骤执行暂停");}}
}

虚拟线程的调度模型

虚拟线程使用ForkJoinPool作为调度器,将虚拟线程调度到平台线程上执行。

当一个虚拟线程执行阻塞操作时,调度器会自动将其挂起,并调度其他虚拟线程到平台线程上执行。

image

这种调度模型使得少量平台线程可以高效地执行大量虚拟线程,极大地提高了系统的并发能力。

六、不同场景下的选择

有些小伙伴可能会问:既然虚拟线程这么强大,是不是应该全部使用虚拟线程呢?其实不然,不同的场景适合不同的并发模型。

1. CPU密集型任务

对于CPU密集型任务(如计算、数据处理),传统线程可能更合适:

// CPU密集型任务示例
public class CpuIntensiveTask {public static void main(String[] args) {int processors = Runtime.getRuntime().availableProcessors();ExecutorService executor = Executors.newFixedThreadPool(processors);for (int i = 0; i < 100; i++) {executor.submit(() -> {// 复杂的计算任务compute();});}executor.shutdown();}private static void compute() {// 模拟CPU密集型计算long result = 0;for (long i = 0; i < 100000000L; i++) {result += i * i;}System.out.println("计算结果: " + result);}
}

2. IO密集型任务

对于IO密集型任务(如网络请求、数据库操作),虚拟线程有明显的优势:

// IO密集型任务示例 - 使用虚拟线程
public class IoIntensiveTask {public static void main(String[] args) {try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {for (int i = 0; i < 10_000; i++) {executor.submit(() -> {// 模拟IO操作String data = httpGet("https://api.example.com/data");processData(data);return null;});}}}private static String httpGet(String url) {// 模拟HTTP请求try {Thread.sleep(100); // 模拟网络延迟return "response data";} catch (InterruptedException e) {throw new RuntimeException(e);}}private static void processData(String data) {// 处理数据System.out.println("处理数据: " + data);}
}

3. 混合型任务

对于既有CPU计算又有IO操作的任务,可以根据具体情况选择:

// 混合型任务示例
public class MixedTask {public static void main(String[] args) {// 对于IO部分使用虚拟线程try (var ioExecutor = Executors.newVirtualThreadPerTaskExecutor()) {List<Future<String>> futures = new ArrayList<>();for (int i = 0; i < 1000; i++) {futures.add(ioExecutor.submit(() -> {// IO操作return fetchData();}));}// 对于CPU密集型部分使用固定线程池int processors = Runtime.getRuntime().availableProcessors();ExecutorService cpuExecutor = Executors.newFixedThreadPool(processors);for (Future<String> future : futures) {cpuExecutor.submit(() -> {try {String data = future.get();// CPU密集型处理processDataIntensively(data);} catch (Exception e) {e.printStackTrace();}});}cpuExecutor.shutdown();}}
}

七、性能对比

为了更直观地展示不同并发模型的性能差异,我们来看一个简单的性能测试:

// 性能对比测试
public class PerformanceComparison {private static final int TASK_COUNT = 10000;private static final int IO_DELAY_MS = 100;public static void main(String[] args) throws InterruptedException {// 测试平台线程long startTime = System.currentTimeMillis();testPlatformThreads();long platformTime = System.currentTimeMillis() - startTime;// 测试虚拟线程startTime = System.currentTimeMillis();testVirtualThreads();long virtualTime = System.currentTimeMillis() - startTime;System.out.println("平台线程耗时: " + platformTime + "ms");System.out.println("虚拟线程耗时: " + virtualTime + "ms");System.out.println("性能提升: " + (double) platformTime / virtualTime + "倍");}private static void testPlatformThreads() throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(200);CountDownLatch latch = new CountDownLatch(TASK_COUNT);for (int i = 0; i < TASK_COUNT; i++) {executor.submit(() -> {try {Thread.sleep(IO_DELAY_MS); // 模拟IO操作} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown();}});}latch.await();executor.shutdown();}private static void testVirtualThreads() throws InterruptedException {try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {CountDownLatch latch = new CountDownLatch(TASK_COUNT);for (int i = 0; i < TASK_COUNT; i++) {executor.submit(() -> {try {Thread.sleep(IO_DELAY_MS); // 模拟IO操作} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown();}});}latch.await();}}
}

测试结果分析

  • 平台线程池(200线程):处理10000个任务约50秒
  • 虚拟线程:处理10000个任务约1秒
  • 性能提升:约50倍

这个测试清楚地展示了虚拟线程在IO密集型场景下的巨大优势。

总结

经过上面的详细讲解,现在我们来总结一下各种并发模型的适用场景和最佳实践:

1. 进程 vs 线程 vs 协程 vs 虚拟线程

特性 进程 线程 协程 虚拟线程
隔离性
创建开销 极小
切换开销
内存占用
并发数量 几十个 几千个 几十万 百万级
适用场景 独立应用 通用并发 特定语言 IO密集型

2. 选择指南

  • 需要完全隔离:选择进程(如微服务架构)
  • CPU密集型任务:选择平台线程池(线程数≈CPU核心数)
  • IO密集型任务:选择虚拟线程(Java 19+)
  • 极高并发需求:考虑协程(如Go语言)或虚拟线程
  • 现有系统迁移:逐步引入虚拟线程,保持兼容性

3. 最佳实践

有些小伙伴在工作中使用并发编程时,可以参考以下最佳实践:

  1. 避免过度使用线程:不要创建过多平台线程
  2. 合理使用线程池:根据任务类型选择合适的线程池
  3. 尝试虚拟线程:在IO密集型场景中尝试使用虚拟线程
  4. 监控线程状态:使用监控工具跟踪线程使用情况
  5. 理解业务特性:根据业务需求选择合适的并发模型

未来展望

虚拟线程是Java并发编程的一次重大飞跃,但它们并不是终点。

随着硬件技术的发展和应用场景的变化,并发模型还会继续演进:

  1. 更好的工具支持:调试、监控工具需要适应虚拟线程
  2. 更优的调度算法:针对不同场景的智能调度
  3. 新的编程模型:响应式编程、actor模型等与虚拟线程的结合
  4. 硬件协同优化:与新一代硬件(如DPU)的协同优化

记住:没有最好的并发模型,只有最适合的并发模型

作为开发者,我们需要根据具体场景做出明智的选择。

最后说一句(求关注,别白嫖我)
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

更多项目实战在我的技术网站:http://www.susan.net.cn/project

http://www.hskmm.com/?act=detail&tid=16686

相关文章:

  • 事倍功半是蠢蛋55 ctrl+shift+f 每次搜索都按倒繁体
  • Ini文件的读写
  • 数据跨境传输解决方案助力企业安全合规高效流通
  • 题解:P9454 [ZSHOI-R1] 巡城
  • QuestaSim奔溃后再次打开无法仿真
  • 软考架构备考-软件可靠性、知识产权和标准化
  • 医院内外网文件传输:平衡安全与效率的关键链路!
  • 我的第一个赚钱网站 -- 从网站源码到集成AdSense获利的全过程
  • Gradle读取仓库配置文件的优先级
  • opencv学习记录5
  • PCS PMA,如何理解硬核IP
  • pycharm中使用调试模式运行 uvicorn.run(app)报错TypeError: _patch_asyncio.locals.run() got an unexpected keywor解决
  • 交换机命令
  • 2025.9.25
  • 易基因:Cell Rep:华农任文凯团队利用ChIP-seq及多组学解析过敏性疾病的关键调控机制|项目文章
  • 详细介绍:STL 容器 --- list 类
  • Idea代码回退已经push到远段仓库的代码分支到指定提交记录
  • 开写第一篇
  • 大模型function calling多轮对话开发示例
  • ViTables 安装与 HDF5 数据可视化全指南 - 实践
  • elementuiplus修改el-checked多选框样式
  • DataGrip格式化SQL模板
  • 计算机毕设java毕业生档案流向追踪系统的设计与实现 基于Java的毕业生档案管理与追踪系统的设计与实现 Java技术驱动的毕业生档案流向监测架构开发
  • 图思维胜过链式思维:JGraphlet构建任务流水线的八大核心原则
  • 两月九城,纷享销客渠道携手伙伴共创CRM新纪元
  • markdown
  • mstsc带用户名密码自动登录
  • Sql Server Begin TRY sample
  • 基于数据平台构建供应链协同体系,实现业务全链路可视化与智能决策
  • 字节二面挂!面试官追问 Redis 内存淘汰策略 LRU 和传统 LRU 差异,我答懵了