摘要:最近准备面试的小伙伴,可以看一下这个宝藏网站(Java 突击队):www.susan.net.cn,里面:面试八股文、场景设计题、面试真题、7个项目实战、工作内推什么都有 。
最近虚拟线程火了。
但有些小伙伴对进程、线程、协程、虚拟线程之间的区别和联系还是没有搞清楚。
今天这篇文章就跟大家一起聊聊,希望对你会有所帮助。
最近准备面试的小伙伴,可以看一下这个宝藏网站(Java 突击队):www.susan.net.cn,里面:面试八股文、场景设计题、面试真题、7个项目实战、工作内推什么都有 。
让我用一个简单的比喻来解释: #技术分享
想象一家大工厂(操作系统):
进程 就像工厂中的一个独立车间,每个车间有自己独立的空间、原料和工具。线程 就像车间中的工人,共享车间的资源,协同完成生产任务。进程是操作系统进行资源分配和调度的基本单位 。
每个进程都有自己独立的地址空间、数据栈、代码段和其他系统资源。
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; } }}进程的特点 :
独立性:每个进程有独立的地址空间,互不干扰安全性:一个进程崩溃不会影响其他进程开销大:创建和销毁进程需要较大的系统开销通信复杂:进程间通信(IPC)需要特殊的机制线程是进程内的执行单元,是 CPU 调度和执行的基本单位 。
一个进程可以包含多个线程,这些线程共享进程的资源。
public class ThreadExample { public static void main(String args) { Thread thread1 = new MyThread; thread1.start; Thread thread2 = new Thread(new MyRunnable); thread2.start; 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); } }要真正理解线程,我们需要深入操作系统层面。
用户级线程完全在用户空间实现,操作系统不知道它们的存在。线程的创建、调度、同步等都由用户级的线程库完成。
优点 :
线程切换不需要陷入内核态,开销小调度算法可以由应用程序自定义不依赖于操作系统支持缺点 :
一个线程阻塞会导致整个进程阻塞无法利用多核CPU的优势内核级线程由操作系统内核直接支持和管理。每个内核线程对应一个内核级的调度实体。
优点 :
一个线程阻塞不会影响其他线程能够利用多核CPU并行执行缺点 :
线程切换需要陷入内核态,开销较大创建线程需要系统调用现代操作系统通常采用混合模型,将用户级线程映射到内核级线程上。
Java 线程就是这种模型的具体实现。
public class ThreadInfoExample { public static void main(String args) { for (int i = 0; i { System.out.println("Java线程: " + Thread.currentThread.getName + ", 操作系统线程ID: " + getThreadId); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace; } }).start; } } private static long getThreadId { return Thread.currentThread.threadId; }}协程是一种比线程更加轻量级的执行单元 ,它由程序员在用户空间控制调度,而不是由操作系统内核调度。
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; }}为了更清晰地理解协程和线程的区别。
有些小伙伴在工作中可能遇到过下面这些的问题。
为了处理大量并发请求,我们创建了大量线程,但很快遇到了瓶颈:
虚拟线程是 JDK 实现的轻量级线程,它们不是由操作系统直接调度,而是由 JDK 调度到平台线程(操作系统线程)上执行。
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 { System.out.println("任务 " + taskId + " 在线程: " + Thread.currentThread); Thread.sleep(1000); return taskId; }); } } }}为了真正理解虚拟线程,我们需要深入其工作原理。
虚拟线程的实现基于一个关键概念:continuation 。
Continuation 表示一个可暂停和恢复的执行上下文。当虚拟线程执行阻塞操作时,JDK 会挂起当前的 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 作为调度器,将虚拟线程调度到平台线程上执行。
当一个虚拟线程执行阻塞操作时,调度器会自动将其挂起,并调度其他虚拟线程到平台线程上执行。
这种调度模型使得少量平台线程可以高效地执行大量虚拟线程,极大地提高了系统的并发能力。
最近建了一些工作内推群,各大城市都有,欢迎各位 HR 和找工作的小伙伴进群交流,群里目前已经收集了不少的工作内推岗位。_su223,备注:掘金+所在城市,即可进群。
有些小伙伴可能会问:既然虚拟线程这么强大,是不是应该全部使用虚拟线程呢?其实不然,不同的场景适合不同的并发模型。
对于 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 { compute; }); } executor.shutdown; } private static void compute { long result = 0; for (long i = 0; i对于 IO 密集型任务(如网络请求、数据库操作),虚拟线程有明显的优势:
public class IoIntensiveTask { public static void main(String args) { try (var executor = Executors.newVirtualThreadPerTaskExecutor) { for (int i = 0; i { String data = httpGet("https://api.example.com/data"); processData(data); return null; }); } } } private static String httpGet(String url) { 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) { try (var ioExecutor = Executors.newVirtualThreadPerTaskExecutor) { List> futures = new ArrayList; for (int i = 0; i { return fetchData; })); } int processors = Runtime.getRuntime.availableProcessors; ExecutorService cpuExecutor = Executors.newFixedThreadPool(processors); for (Future future : futures) { cpuExecutor.submit( -> { try { String data = future.get; 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 { try { Thread.sleep(IO_DELAY_MS); } 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 { try { Thread.sleep(IO_DELAY_MS); } catch (InterruptedException e) { e.printStackTrace; } finally { latch.countDown; } }); } latch.await; } }}测试结果分析 :
平台线程池(200线程):处理10000个任务约50秒虚拟线程:处理10000个任务约1秒性能提升:约50倍这个测试清楚地展示了虚拟线程在 IO 密集型场景下的巨大优势。
总结虚拟线程是 Java 并发编程的一次重大飞跃,但它们并不是终点。
更好的工具支持 :调试、监控工具需要适应虚拟线程更优的调度算法 :针对不同场景的智能调度新的编程模型 :响应式编程、actor模型等与虚拟线程的结合硬件协同优化 :与新一代硬件(如DPU)的协同优化记住: 没有最好的并发模型,只有最适合的并发模型 。
作为开发者,我们需要根据具体场景做出明智的选择。
来源:墨码行者