找回密码
 立即注册
首页 业界区 业界 进程、线程、协程、虚拟线程,傻傻分不清楚 ...

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

蓝娅萍 5 天前
前言

最近虚拟线程火了。
但有些小伙伴对进程、线程、协程、虚拟线程之间的区别和联系还是没有搞清楚。
今天这篇文章就跟大家一起聊聊,希望对你会有所帮助。
一、进程与线程

有些小伙伴在工作中可能经常听到"进程"和"线程"这两个词,但未必真正理解它们之间的本质区别。
让我用一个简单的比喻来解释:
想象一家大工厂(操作系统):

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

进程是操作系统进行资源分配和调度的基本单位
每个进程都有自己独立的地址空间、数据栈、代码段和其他系统资源。
  1. // Java中创建进程的示例
  2. public class ProcessExample {
  3.     public static void main(String[] args) throws IOException {
  4.         // 启动一个新的进程(比如打开计算器)
  5.         ProcessBuilder processBuilder = new ProcessBuilder("calc.exe");
  6.         Process process = processBuilder.start();
  7.         
  8.         System.out.println("进程ID: " + process.pid());
  9.         System.out.println("是否存活: " + process.isAlive());
  10.         
  11.         // 等待进程结束
  12.         try {
  13.             int exitCode = process.waitFor();
  14.             System.out.println("进程退出码: " + exitCode);
  15.         } catch (InterruptedException e) {
  16.             e.printStackTrace();
  17.         }
  18.     }
  19. }
复制代码
进程的特点

  • 独立性:每个进程有独立的地址空间,互不干扰
  • 安全性:一个进程崩溃不会影响其他进程
  • 开销大:创建和销毁进程需要较大的系统开销
  • 通信复杂:进程间通信(IPC)需要特殊的机制
线程:轻量级的执行单元

线程是进程内的执行单元,是CPU调度和执行的基本单位
一个进程可以包含多个线程,这些线程共享进程的资源。
  1. // Java中创建线程的两种方式
  2. public class ThreadExample {
  3.     public static void main(String[] args) {
  4.         // 方式1:继承Thread类
  5.         Thread thread1 = new MyThread();
  6.         thread1.start();
  7.         
  8.         // 方式2:实现Runnable接口
  9.         Thread thread2 = new Thread(new MyRunnable());
  10.         thread2.start();
  11.         
  12.         // 方式3:使用Lambda表达式
  13.         Thread thread3 = new Thread(() -> {
  14.             System.out.println("Lambda线程执行: " + Thread.currentThread().getName());
  15.         });
  16.         thread3.start();
  17.     }
  18. }
  19. class MyThread extends Thread {
  20.     @Override
  21.     public void run() {
  22.         System.out.println("MyThread执行: " + Thread.currentThread().getName());
  23.     }
  24. }
  25. class MyRunnable implements Runnable {
  26.     @Override
  27.     public void run() {
  28.         System.out.println("MyRunnable执行: " + Thread.currentThread().getName());
  29.     }
  30. }
复制代码
线程的特点

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

要真正理解线程,我们需要深入操作系统层面。
现代操作系统通常采用三种线程模型:
1. 用户级线程(ULT)

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

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

  • 一个线程阻塞会导致整个进程阻塞
  • 无法利用多核CPU的优势
2. 内核级线程(KLT)

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

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

  • 线程切换需要陷入内核态,开销较大
  • 创建线程需要系统调用
3. 混合模型

现代操作系统通常采用混合模型,将用户级线程映射到内核级线程上。
Java线程就是这种模型的具体实现。
  1. // Java线程与操作系统线程的对应关系
  2. public class ThreadInfoExample {
  3.     public static void main(String[] args) {
  4.         // 创建多个线程
  5.         for (int i = 0; i < 5; i++) {
  6.             int threadId = i;
  7.             new Thread(() -> {
  8.                 System.out.println("Java线程: " + Thread.currentThread().getName() +
  9.                                  ", 操作系统线程ID: " + getThreadId());
  10.                 try {
  11.                     Thread.sleep(5000);
  12.                 } catch (InterruptedException e) {
  13.                     e.printStackTrace();
  14.                 }
  15.             }).start();
  16.         }
  17.     }
  18.    
  19.     // 获取操作系统线程ID(Java 10+)
  20.     private static long getThreadId() {
  21.         return Thread.currentThread().threadId();
  22.     }
  23. }
复制代码
三、协程

有些小伙伴可能听说过协程(Coroutine),尤其是在Go语言中非常流行。
那么协程和线程有什么区别呢?
协程的本质

协程是一种比线程更加轻量级的执行单元,它由程序员在用户空间控制调度,而不是由操作系统内核调度。
  1. // Java中可以使用第三方库实现协程(如Quasar)
  2. // 以下是伪代码示例,展示协程的概念
  3. public class CoroutineExample {
  4.     public static void main(String[] args) {
  5.         // 创建协程
  6.         Coroutine coroutine1 = new Coroutine(() -> {
  7.             System.out.println("协程1开始");
  8.             Coroutine.yield(); // 主动让出执行权
  9.             System.out.println("协程1继续");
  10.         });
  11.         
  12.         Coroutine coroutine2 = new Coroutine(() -> {
  13.             System.out.println("协程2开始");
  14.             Coroutine.yield(); // 主动让出执行权
  15.             System.out.println("协程2继续");
  16.         });
  17.         
  18.         // 手动调度协程
  19.         coroutine1.run();
  20.         coroutine2.run();
  21.         coroutine1.run();
  22.         coroutine2.run();
  23.     }
  24. }
复制代码
协程的特点

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

为了更清晰地理解协程和线程的区别。
我们先看看执行单元的对比图:
1.png

再看看创建数量的对比图:
2.png

四、虚拟线程

Java 19引入了虚拟线程(Virtual Threads),这是Java并发模型的一次重大革新。
虚拟线程旨在解决传统平台线程的局限性。
为什么需要虚拟线程?

有些小伙伴在工作中可能遇到过下面这些的问题。
为了处理大量并发请求,我们创建了大量线程,但很快遇到了瓶颈:

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

虚拟线程是JDK实现的轻量级线程,它们不是由操作系统直接调度,而是由JDK调度到平台线程(操作系统线程)上执行。
  1. // Java 19+ 虚拟线程使用示例
  2. public class VirtualThreadExample {
  3.     public static void main(String[] args) throws InterruptedException {
  4.         // 创建虚拟线程
  5.         Thread virtualThread = Thread.ofVirtual().start(() -> {
  6.             System.out.println("虚拟线程执行: " + Thread.currentThread());
  7.             try {
  8.                 Thread.sleep(1000);
  9.             } catch (InterruptedException e) {
  10.                 e.printStackTrace();
  11.             }
  12.         });
  13.         
  14.         // 等待虚拟线程结束
  15.         virtualThread.join();
  16.         
  17.         // 使用虚拟线程处理大量任务
  18.         try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
  19.             for (int i = 0; i < 10_000; i++) {
  20.                 int taskId = i;
  21.                 executor.submit(() -> {
  22.                     System.out.println("任务 " + taskId + " 在线程: " + Thread.currentThread());
  23.                     Thread.sleep(1000);
  24.                     return taskId;
  25.                 });
  26.             }
  27.         }
  28.     }
  29. }
复制代码
虚拟线程的优势


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

为了真正理解虚拟线程,我们需要深入其工作原理。
虚拟线程的实现基于一个关键概念:continuation
Continuation的概念

Continuation表示一个可暂停和恢复的执行上下文。当虚拟线程执行阻塞操作时,JDK会挂起当前的continuation,并释放平台线程去执行其他任务。
  1. // 伪代码:展示continuation的概念
  2. public class ContinuationExample {
  3.     public static void main(String[] args) {
  4.         ContinuationScope scope = new ContinuationScope("example");
  5.         
  6.         Continuation continuation = new Continuation(scope, () -> {
  7.             System.out.println("步骤1");
  8.             Continuation.yield(scope); // 暂停执行
  9.             
  10.             System.out.println("步骤2");
  11.             Continuation.yield(scope); // 暂停执行
  12.             
  13.             System.out.println("步骤3");
  14.         });
  15.         
  16.         // 分步执行
  17.         while (!continuation.isDone()) {
  18.             System.out.println("开始执行步骤...");
  19.             continuation.run();
  20.             System.out.println("步骤执行暂停");
  21.         }
  22.     }
  23. }
复制代码
虚拟线程的调度模型

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

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

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

对于CPU密集型任务(如计算、数据处理),传统线程可能更合适:
  1. // CPU密集型任务示例
  2. public class CpuIntensiveTask {
  3.     public static void main(String[] args) {
  4.         int processors = Runtime.getRuntime().availableProcessors();
  5.         ExecutorService executor = Executors.newFixedThreadPool(processors);
  6.         
  7.         for (int i = 0; i < 100; i++) {
  8.             executor.submit(() -> {
  9.                 // 复杂的计算任务
  10.                 compute();
  11.             });
  12.         }
  13.         
  14.         executor.shutdown();
  15.     }
  16.    
  17.     private static void compute() {
  18.         // 模拟CPU密集型计算
  19.         long result = 0;
  20.         for (long i = 0; i < 100000000L; i++) {
  21.             result += i * i;
  22.         }
  23.         System.out.println("计算结果: " + result);
  24.     }
  25. }
复制代码
2. IO密集型任务

对于IO密集型任务(如网络请求、数据库操作),虚拟线程有明显的优势:
  1. // IO密集型任务示例 - 使用虚拟线程
  2. public class IoIntensiveTask {
  3.     public static void main(String[] args) {
  4.         try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
  5.             for (int i = 0; i < 10_000; i++) {
  6.                 executor.submit(() -> {
  7.                     // 模拟IO操作
  8.                     String data = httpGet("https://api.example.com/data");
  9.                     processData(data);
  10.                     return null;
  11.                 });
  12.             }
  13.         }
  14.     }
  15.    
  16.     private static String httpGet(String url) {
  17.         // 模拟HTTP请求
  18.         try {
  19.             Thread.sleep(100); // 模拟网络延迟
  20.             return "response data";
  21.         } catch (InterruptedException e) {
  22.             throw new RuntimeException(e);
  23.         }
  24.     }
  25.    
  26.     private static void processData(String data) {
  27.         // 处理数据
  28.         System.out.println("处理数据: " + data);
  29.     }
  30. }
复制代码
3. 混合型任务

对于既有CPU计算又有IO操作的任务,可以根据具体情况选择:
  1. // 混合型任务示例
  2. public class MixedTask {
  3.     public static void main(String[] args) {
  4.         // 对于IO部分使用虚拟线程
  5.         try (var ioExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
  6.             List<Future<String>> futures = new ArrayList<>();
  7.             
  8.             for (int i = 0; i < 1000; i++) {
  9.                 futures.add(ioExecutor.submit(() -> {
  10.                     // IO操作
  11.                     return fetchData();
  12.                 }));
  13.             }
  14.             
  15.             // 对于CPU密集型部分使用固定线程池
  16.             int processors = Runtime.getRuntime().availableProcessors();
  17.             ExecutorService cpuExecutor = Executors.newFixedThreadPool(processors);
  18.             
  19.             for (Future<String> future : futures) {
  20.                 cpuExecutor.submit(() -> {
  21.                     try {
  22.                         String data = future.get();
  23.                         // CPU密集型处理
  24.                         processDataIntensively(data);
  25.                     } catch (Exception e) {
  26.                         e.printStackTrace();
  27.                     }
  28.                 });
  29.             }
  30.             
  31.             cpuExecutor.shutdown();
  32.         }
  33.     }
  34. }
复制代码
七、性能对比

为了更直观地展示不同并发模型的性能差异,我们来看一个简单的性能测试:
  1. // 性能对比测试
  2. public class PerformanceComparison {
  3.     private static final int TASK_COUNT = 10000;
  4.     private static final int IO_DELAY_MS = 100;
  5.    
  6.     public static void main(String[] args) throws InterruptedException {
  7.         // 测试平台线程
  8.         long startTime = System.currentTimeMillis();
  9.         testPlatformThreads();
  10.         long platformTime = System.currentTimeMillis() - startTime;
  11.         
  12.         // 测试虚拟线程
  13.         startTime = System.currentTimeMillis();
  14.         testVirtualThreads();
  15.         long virtualTime = System.currentTimeMillis() - startTime;
  16.         
  17.         System.out.println("平台线程耗时: " + platformTime + "ms");
  18.         System.out.println("虚拟线程耗时: " + virtualTime + "ms");
  19.         System.out.println("性能提升: " + (double) platformTime / virtualTime + "倍");
  20.     }
  21.    
  22.     private static void testPlatformThreads() throws InterruptedException {
  23.         ExecutorService executor = Executors.newFixedThreadPool(200);
  24.         CountDownLatch latch = new CountDownLatch(TASK_COUNT);
  25.         
  26.         for (int i = 0; i < TASK_COUNT; i++) {
  27.             executor.submit(() -> {
  28.                 try {
  29.                     Thread.sleep(IO_DELAY_MS); // 模拟IO操作
  30.                 } catch (InterruptedException e) {
  31.                     e.printStackTrace();
  32.                 } finally {
  33.                     latch.countDown();
  34.                 }
  35.             });
  36.         }
  37.         
  38.         latch.await();
  39.         executor.shutdown();
  40.     }
  41.    
  42.     private static void testVirtualThreads() throws InterruptedException {
  43.         try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
  44.             CountDownLatch latch = new CountDownLatch(TASK_COUNT);
  45.             
  46.             for (int i = 0; i < TASK_COUNT; i++) {
  47.                 executor.submit(() -> {
  48.                     try {
  49.                         Thread.sleep(IO_DELAY_MS); // 模拟IO操作
  50.                     } catch (InterruptedException e) {
  51.                         e.printStackTrace();
  52.                     } finally {
  53.                         latch.countDown();
  54.                     }
  55.                 });
  56.             }
  57.             
  58.             latch.await();
  59.         }
  60.     }
  61. }
复制代码
测试结果分析

  • 平台线程池(200线程):处理10000个任务约50秒
  • 虚拟线程:处理10000个任务约1秒
  • 性能提升:约50倍
这个测试清楚地展示了虚拟线程在IO密集型场景下的巨大优势。
总结

经过上面的详细讲解,现在我们来总结一下各种并发模型的适用场景和最佳实践:
1. 进程 vs 线程 vs 协程 vs 虚拟线程

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


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

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

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

虚拟线程是Java并发编程的一次重大飞跃,但它们并不是终点。
随着硬件技术的发展和应用场景的变化,并发模型还会继续演进:

  • 更好的工具支持:调试、监控工具需要适应虚拟线程
  • 更优的调度算法:针对不同场景的智能调度
  • 新的编程模型:响应式编程、actor模型等与虚拟线程的结合
  • 硬件协同优化:与新一代硬件(如DPU)的协同优化
记住:没有最好的并发模型,只有最适合的并发模型
作为开发者,我们需要根据具体场景做出明智的选择。
最后说一句(求关注,别白嫖我)
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
更多项目实战在我的技术网站:http://www.susan.net.cn/project

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册