三、核心类库改进

444 Virtual Threads 虚拟线程 

 虚拟线程 Virtual Threads,也称为“用户模式线程(user-mode threads)”或“纤程(fibers)”。 

 Java21中新引入虚拟线程是一种线程抽象,它提供了一种轻量级的线程实现方式,可以在用户级别进行线程调度和管理。

     虚拟线程提供了一种更高效、更轻量级的线程模型,可以在Java应用程序中创建数百万甚至数十亿个线程,而不会受到操作系统线程数量的限制。这使得Java应用程序能够更好地适应大规模并发场景,并提供更高的性能和可伸缩性。

    而传统的平台线程是重量级的线程,即它们使用大量计算资源,且与os线程1:1绑定,这意味着线程执行时间依赖CPU执行时间。由于操作系统线程的数量有限,当应用程序需要创建大量线程时,可能会导致资源耗尽或性能下降。

虚拟线程与传统线程核心区别

1)传统Java线程是由操作系统级别的线程,需要在JVM外部进行管理和调度。

2)虚拟线程则是由JVM内部实现的,可以在JVM内部进行管理和调度。

维度 虚拟线程(Virtual Thread) 传统线程(Carrier Java Thread)
资源消耗 轻量级(仅JVM内部调度开销) 重(依赖操作系统内核线程)
并发能力 支持百万级并发 受操作系统线程数限制(通常数千)
调度粒度 JVM内部调度(用户态) 操作系统调度(内核态)
适用场景 高并发、短任务(如Web请求) 长任务、需系统资源的场景

虚拟线程的工作原理

       传统的物理线程(也叫平台线程)是基于操作系统线程来实现的,而操作系统线程属于重量级的线程,创建和上下文切换都是非常消耗系统资源的;而虚拟线程是一种更加轻量级的线程,它打破了平台线程和操作系统线程之间一一对应的限制,咱们可以轻松地创建出百万甚至干万级别的线程。      

       虚拟线程是工作在物理线程(平台线程)之上的,任务最终还是被物理线程执行的,优点是虚拟线程在等待的时候会将它持有的物理线程释放,这样其他的虚拟线程就可以获得这些被释放的物理线程继续执行。

        平台线程是被操作系统调度的,而虚拟线程是直接被JDK调度的,因此虚拟线程在切换时可以显著地降低开销。

       

高并发文件下载器

     如果需要同时下载1000个网络文件(IO密集型任务),对比‌传统平台线程‌与‌虚拟线程‌的实现差异及效果。

一、传统平台线程方案(问题暴露)

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class PlatformThreadDownloader2 {

    public static void download(String url) {
        // 模拟网络IO:建立连接、读取文件(阻塞操作)
        try {
            Thread.sleep(1000); // 模拟IO耗时1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 实现下载逻辑
        System.out.println("下载完成:" + url);
    }

    public static void main(String[] args) {
        // 记录总开始时间
        long totalStartTime = System.currentTimeMillis();

        // 配置参数
        int threadCount = 200;
        int taskCount = 1000;

       // 传统线程池:假设核心线程数设为200(受限于内存和系统资源)
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);

        // 使用CountDownLatch来同步所有任务的完成
        CountDownLatch latch = new CountDownLatch(taskCount);

        // 模拟1000个下载任务
        for (int i = 0; i < taskCount; i++) {
            int taskId = i;
            // 提交任务到线程池
            executor.execute(() -> {
                // 模拟下载操作
                download("http://example.com/file" + taskId+".txt");

                // 计数器减1
                latch.countDown();
            });
        }

        try {
            // 等待所有任务完成,最多等待10分钟
            boolean completed = latch.await(10, TimeUnit.MINUTES);

            if (!completed) {
                System.out.println("警告:部分任务执行超时!");
                executor.shutdownNow();
                System.out.println("已强制终止超时任务");
                return;
            }

            // 优雅关闭线程池
            executor.shutdown();


            long totalEndTime = System.currentTimeMillis();
            // 计算总耗时
            long totalCost = totalEndTime - totalStartTime;

            System.out.println("\n=== 执行结果统计 ===");
            System.out.println("任务总数: " + taskCount);
            System.out.println("线程池大小: " + threadCount);
            System.out.println("总耗时: " + totalCost + "ms");
            System.out.println("平均每个任务耗时: " + (totalCost / (double)taskCount) + "ms");
            System.out.println("吞吐量: " + (taskCount / (totalCost / 1000.0)) + " 任务/秒");

        } catch (InterruptedException e) {
            System.out.println("程序被中断");
            Thread.currentThread().interrupt();
            executor.shutdownNow();
        }
    }

}


问题分析:

1.资源限制‌:

      每个平台线程默认栈内存1MB,200个线程占用约200MB内存。若强行创建1000个平台线程,内存占用达1GB+,可能触发OutOfMemoryError

2. ‌任务排队‌:

     1000个任务需在200个线程中排队执行,总耗时≈(1000/200) * 1秒 = 5秒,吞吐量低。

3.‌CPU利用率低‌:

      线程在downloadFile中99%时间处于IO阻塞(Thread.sleep模拟),但仍占用线程资源,导致CPU空闲。

二、虚拟线程方案(优化对比)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualThreadDownLoader {

    public static void main(String[] args) {

        long start = System.nanoTime();  // 纳秒级精度: 1毫秒 = 1,000,000纳秒
        // 虚拟线程池:自动调度到少量平台线程(载体线程)
        try(ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();){
            //直接创建1000个虚拟线程任务
            for (int i = 0; i <1000 ; i++) {
                int taskId = i;
                executor.execute(() -> download("http://example.com/file" + taskId+".txt"));
            }
        }
        long end = System.nanoTime();
        System.out.println("耗时:" + (end - start) / 1_000_000 + "ms");
    }

    public static void download(String url) {
        //  模拟网络IO:虚拟线程在阻塞时自动释放载体线程,不影响其他虚拟线程的执行
        try {
            Thread.sleep(1000); // 阻塞时JVM自动挂起虚拟线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 实现下载逻辑
        System.out.println("下载完成:" + url);
    }
}

优化分析:

  1. 超轻量级资源占用‌:
    • 每个虚拟线程初始栈内存仅几百字节,1000个虚拟线程总内存仅几MB,远低于平台线程。
  2. M调度复用载体线程‌:
    • JVM自动将1000个虚拟线程调度到少量平台线程(如8核CPU可能仅用8个载体线程)。
    • 虚拟线程执行Thread.sleep(IO阻塞)时,JVM自动挂起该线程并释放载体线程,供其他虚拟线程使用。
  3. 吞吐量提升‌:

       1000个任务可‌并行阻塞‌(IO等待期间不占用载体线程),总耗时≈1秒(所有任务同时开始IO,1秒后全部完成),吞吐量提升5倍。

总之:

  • 传统方案‌:200个线程需5秒完成1000个任务,内存占用200MB+。
  • 虚拟线程方案‌:8个载体线程1秒完成1000个任务,内存占用仅几MB,CPU利用率接近100%。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐