最近虚拟线程火了,但有些小伙伴对进程、线程、协程、虚拟线程之间的区别和联系还是没有搞清楚。
今天这篇文章就跟大家一起聊聊,希望对你会有所帮助。
有些小伙伴在工作中可能经常听到"进程"和"线程"这两个词,但未必真正理解它们之间的本质区别。
进程
线程
让我用一个简单的比喻来解释:
想象一家大工厂(操作系统):
进程是操作系统进行资源分配和调度的基本单位。
每个进程都有自己独立的地址空间、数据栈、代码段和其他系统资源。
// 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(); } } }
进程的特点:
线程是进程内的执行单元,是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 { @Override public void run() { System.out.println("MyThread执行: " + Thread.currentThread().getName()); } } class MyRunnable implements Runnable { @Override public void run() { System.out.println("MyRunnable执行: " + Thread.currentThread().getName()); } }
线程的特点:
要真正理解线程,我们需要深入操作系统层面。
现代操作系统通常采用三种线程模型:
用户级线程完全在用户空间实现,操作系统不知道它们的存在。线程的创建、调度、同步等都由用户级的线程库完成。
优点:
缺点:
内核级线程由操作系统内核直接支持和管理。每个内核线程对应一个内核级的调度实体。
现代操作系统通常采用混合模型,将用户级线程映射到内核级线程上。
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(); } }
协程的特点:
为了更清晰地理解协程和线程的区别。
我们先看看执行单元的对比图:
再看看创建数量的对比图:
Java 19引入了虚拟线程(Virtual Threads),这是Java并发模型的一次重大革新。
虚拟线程旨在解决传统平台线程的局限性。
有些小伙伴在工作中可能遇到过下面这些的问题。
为了处理大量并发请求,我们创建了大量线程,但很快遇到了瓶颈:
虚拟线程是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; }); } } } }
为了真正理解虚拟线程,我们需要深入其工作原理。
虚拟线程的实现基于一个关键概念: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作为调度器,将虚拟线程调度到平台线程上执行。
当一个虚拟线程执行阻塞操作时,调度器会自动将其挂起,并调度其他虚拟线程到平台线程上执行。
这种调度模型使得少量平台线程可以高效地执行大量虚拟线程,极大地提高了系统的并发能力。
有些小伙伴可能会问:既然虚拟线程这么强大,是不是应该全部使用虚拟线程呢?其实不然,不同的场景适合不同的并发模型。
对于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); } }
对于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); } }
对于既有CPU计算又有IO操作的任务,可以根据具体情况选择:
// 混合型任务示例 public class MixedTask { public static void main(String[] args) { // 对于IO部分使用虚拟线程 try (var ioExecutor = Executors.newVirtualThreadPerTaskExecutor()) { List> 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 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(); } } }
测试结果分析:
这个测试清楚地展示了虚拟线程在IO密集型场景下的巨大优势。
经过上面的详细讲解,现在我们来总结一下各种并发模型的适用场景和最佳实践:
有些小伙伴在工作中使用并发编程时,可以参考以下最佳实践:
虚拟线程是Java并发编程的一次重大飞跃,但它们并不是终点。
随着硬件技术的发展和应用场景的变化,并发模型还会继续演进:
记住:没有最好的并发模型,只有最适合的并发模型。
原作者:苏三说技术(公众号同名)
原文转自:博客园
原文地址:进程、线程、协程、虚拟线程,傻傻分不清楚
很久没水贴了,水一篇吧
涨见识了,才知道还有协程这个东西,以后有时间再深入学习一下。(当然我说的是c++)
Featured Collection
Popular Ranking
Popular Events
前言
最近虚拟线程火了,但有些小伙伴对进程、线程、协程、虚拟线程之间的区别和联系还是没有搞清楚。
今天这篇文章就跟大家一起聊聊,希望对你会有所帮助。
一、进程与线程
有些小伙伴在工作中可能经常听到"
进程
"和"线程
"这两个词,但未必真正理解它们之间的本质区别。让我用一个简单的比喻来解释:
想象一家大工厂(操作系统):
进程:独立的执行环境
进程是操作系统进行资源分配和调度的基本单位。
每个进程都有自己独立的地址空间、数据栈、代码段和其他系统资源。
进程的特点:
线程:轻量级的执行单元
线程是进程内的执行单元,是CPU调度和执行的基本单位。
一个进程可以包含多个线程,这些线程共享进程的资源。
线程的特点:
二、线程的深入剖析
要真正理解线程,我们需要深入操作系统层面。
现代操作系统通常采用三种线程模型:
1. 用户级线程(ULT)
用户级线程完全在用户空间实现,操作系统不知道它们的存在。线程的创建、调度、同步等都由用户级的线程库完成。
优点:
缺点:
2. 内核级线程(KLT)
内核级线程由操作系统内核直接支持和管理。每个内核线程对应一个内核级的调度实体。
优点:
缺点:
3. 混合模型
现代操作系统通常采用混合模型,将用户级线程映射到内核级线程上。
Java线程就是这种模型的具体实现。
三、协程
有些小伙伴可能听说过协程(Coroutine),尤其是在Go语言中非常流行。
那么协程和线程有什么区别呢?
协程的本质
协程是一种比线程更加轻量级的执行单元,它由程序员在用户空间控制调度,而不是由操作系统内核调度。
协程的特点:
协程 vs 线程
为了更清晰地理解协程和线程的区别。
我们先看看执行单元的对比图:
再看看创建数量的对比图:
四、虚拟线程
Java 19引入了虚拟线程(Virtual Threads),这是Java并发模型的一次重大革新。
虚拟线程旨在解决传统平台线程的局限性。
为什么需要虚拟线程?
有些小伙伴在工作中可能遇到过下面这些的问题。
为了处理大量并发请求,我们创建了大量线程,但很快遇到了瓶颈:
虚拟线程的实现原理
虚拟线程是JDK实现的轻量级线程,它们不是由操作系统直接调度,而是由JDK调度到平台线程(操作系统线程)上执行。
虚拟线程的优势
五、虚拟线程如何工作?
为了真正理解虚拟线程,我们需要深入其工作原理。
虚拟线程的实现基于一个关键概念:continuation。
Continuation的概念
Continuation表示一个可暂停和恢复的执行上下文。当虚拟线程执行阻塞操作时,JDK会挂起当前的continuation,并释放平台线程去执行其他任务。
虚拟线程的调度模型
虚拟线程使用ForkJoinPool作为调度器,将虚拟线程调度到平台线程上执行。
当一个虚拟线程执行阻塞操作时,调度器会自动将其挂起,并调度其他虚拟线程到平台线程上执行。
这种调度模型使得少量平台线程可以高效地执行大量虚拟线程,极大地提高了系统的并发能力。
六、不同场景下的选择
有些小伙伴可能会问:既然虚拟线程这么强大,是不是应该全部使用虚拟线程呢?其实不然,不同的场景适合不同的并发模型。
1. CPU密集型任务
对于CPU密集型任务(如计算、数据处理),传统线程可能更合适:
2. IO密集型任务
对于IO密集型任务(如网络请求、数据库操作),虚拟线程有明显的优势:
3. 混合型任务
对于既有CPU计算又有IO操作的任务,可以根据具体情况选择:
七、性能对比
为了更直观地展示不同并发模型的性能差异,我们来看一个简单的性能测试:
测试结果分析:
这个测试清楚地展示了虚拟线程在IO密集型场景下的巨大优势。
总结
经过上面的详细讲解,现在我们来总结一下各种并发模型的适用场景和最佳实践:
1. 进程 vs 线程 vs 协程 vs 虚拟线程
2. 选择指南
3. 最佳实践
有些小伙伴在工作中使用并发编程时,可以参考以下最佳实践:
未来展望
虚拟线程是Java并发编程的一次重大飞跃,但它们并不是终点。
随着硬件技术的发展和应用场景的变化,并发模型还会继续演进:
原作者:苏三说技术(公众号同名)
原文转自:博客园
原文地址:进程、线程、协程、虚拟线程,傻傻分不清楚