Java学习——————————虚拟线程

        虚拟线程是Java 19提出并在java21正式引入的一项革命性并发特性,作为Project Loom项目的核心成果,它彻底改变了Java处理高并发场景的方式。其是JVM管理的轻量级线程,与传统操作系统线程(即平台线程)相比具有本质区别。它采用M:N调度模型,即多个虚拟线程(M)映射到少量平台线程(N)上执行,由JVM而非操作系统负责调度。这种设计使虚拟线程具有以下核心特性:

        轻量级:每个虚拟线程仅需约1KB内存,而传统线程通常需要1MB栈空间

        高并发:可轻松创建数百万个虚拟线程,突破物理线程限制

        低成本:虚拟线程的创建和切换开销极低,上下文切换由JVM在用户空间完成

        兼容性:其保持与传统Thread API相同的编程模型,学习成本低

        虚拟线程是基于Continuation(延续)机制实现,当其遇到阻塞操作(如I/O)时,JVM会将虚拟线程状态保存到堆内存并挂起,释放载体线程执行其他任务;当阻塞操作完成,再从堆中恢复状态继续执行。这种"挂起-恢复"机制使得: I/O等待不再浪费线程资源;少量载体线程可服务大量并发请求;编程模型保持同步风格,但获得异步性能。

        

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

public class ThreadComparisonDemo {
    // 总任务数
    private static final int TASK_COUNT = 10000;
    // 每个任务的模拟处理时间(毫秒)
    private static final int TASK_PROCESSING_TIME = 100;
    public static void main(String[] args) {
        System.out.println("开始线程性能对比测试");
        System.out.println("任务数量: " + TASK_COUNT + " | 每个任务处理时间: " + TASK_PROCESSING_TIME + "ms");
        try {
            testPlatformThreads();  // 测试传统线程池
            testVirtualThreads(); // 测试虚拟线程
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("测试被中断");
        }
    }

    private static void testPlatformThreads() throws InterruptedException {
        System.out.println("\n===== 测试传统线程池 =====");
        // 传统线程使用固定大小的线程池(通常设置为CPU核心数)
        int poolSize = Runtime.getRuntime().availableProcessors();
        ExecutorService executor = Executors.newFixedThreadPool(poolSize);
        System.out.println("线程池大小: " + poolSize);//注意:传统线程池受限于线程数量,任务将排队等待执行
        CountDownLatch latch = new CountDownLatch(TASK_COUNT);
        long startTime = System.currentTimeMillis();
        // 提交所有任务
        for (int i = 0; i < TASK_COUNT; i++) {
            final int taskId = i;
            executor.execute(() -> {
                try {
                    Thread.sleep(TASK_PROCESSING_TIME);// // 模拟任务处理(I/O等待、数据库操作等)

                    if (taskId % 1000 == 0) { // 每完成1000个任务打印一次进度
                        System.out.printf("传统线程池 - 已完成任务: %,d/%d%n",
                                TASK_COUNT - latch.getCount(), TASK_COUNT);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }
        latch.await();// 等待所有任务完成
        long endTime = System.currentTimeMillis();
        executor.shutdown();
        System.out.println("传统线程池 - 所有任务完成");
        System.out.printf("总耗时: %,d 毫秒%n", (endTime - startTime));
        System.out.printf("平均每个任务耗时: %.2f 毫秒%n",
                (double)(endTime - startTime) / TASK_COUNT);
    }

    private static void testVirtualThreads() throws InterruptedException {
        System.out.println("\n===== 测试虚拟线程 =====");//虚拟线程将为每个任务创建独立线程,无数量限制
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();// 使用虚拟线程执行器
        CountDownLatch latch = new CountDownLatch(TASK_COUNT);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < TASK_COUNT; i++) {// 提交所有任务
            final int taskId = i;
            executor.execute(() -> {
                try {
                    Thread.sleep(TASK_PROCESSING_TIME);// 模拟任务处理
                    if (taskId % 1000 == 0) { // 每完成1000个任务打印一次进度
                        System.out.printf("虚拟线程 - 已完成任务: %,d/%d (线程: %s)%n",
                                TASK_COUNT - latch.getCount(), TASK_COUNT,
                                Thread.currentThread());
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }
        latch.await();// 等待所有任务完成
        long endTime = System.currentTimeMillis();
        executor.close();
        System.out.println("虚拟线程 - 所有任务完成");
        System.out.printf("总耗时: %,d 毫秒%n", (endTime - startTime));
        System.out.printf("平均每个任务耗时: %.2f 毫秒%n",
                (double)(endTime - startTime) / TASK_COUNT);
        System.out.println("\n虚拟线程示例信息:");// 打印虚拟线程信息
        System.out.println("最后一个虚拟线程: " + Thread.currentThread());
        System.out.println("是否为虚拟线程: " + Thread.currentThread().isVirtual());
    }
}

        其运行结果如下:

开始线程性能对比测试
任务数量: 10000 | 每个任务处理时间: 100ms

===== 测试传统线程池 =====
线程池大小: 16
传统线程池 - 已完成任务: 14/10000
传统线程池 - 已完成任务: 1,004/10000
传统线程池 - 已完成任务: 2,011/10000
传统线程池 - 已完成任务: 2,999/10000
传统线程池 - 已完成任务: 4,003/10000
传统线程池 - 已完成任务: 4,998/10000
传统线程池 - 已完成任务: 6,009/10000
传统线程池 - 已完成任务: 6,997/10000
传统线程池 - 已完成任务: 8,011/10000
传统线程池 - 已完成任务: 9,006/10000
传统线程池 - 所有任务完成
总耗时: 68,897 毫秒
平均每个任务耗时: 6.89 毫秒

===== 测试虚拟线程 =====
虚拟线程 - 已完成任务: 3/10000 (线程: VirtualThread[#46]/runnable@ForkJoinPool-1-worker-13)
虚拟线程 - 已完成任务: 1,000/10000 (线程: VirtualThread[#1063]/runnable@ForkJoinPool-1-worker-10)
虚拟线程 - 已完成任务: 2,000/10000 (线程: VirtualThread[#2063]/runnable@ForkJoinPool-1-worker-4)
虚拟线程 - 已完成任务: 3,000/10000 (线程: VirtualThread[#3063]/runnable@ForkJoinPool-1-worker-1)
虚拟线程 - 已完成任务: 4,000/10000 (线程: VirtualThread[#4070]/runnable@ForkJoinPool-1-worker-5)
虚拟线程 - 已完成任务: 5,001/10000 (线程: VirtualThread[#5070]/runnable@ForkJoinPool-1-worker-2)
虚拟线程 - 已完成任务: 6,001/10000 (线程: VirtualThread[#6070]/runnable@ForkJoinPool-1-worker-10)
虚拟线程 - 已完成任务: 7,000/10000 (线程: VirtualThread[#7070]/runnable@ForkJoinPool-1-worker-16)
虚拟线程 - 已完成任务: 8,001/10000 (线程: VirtualThread[#8070]/runnable@ForkJoinPool-1-worker-3)
虚拟线程 - 已完成任务: 9,001/10000 (线程: VirtualThread[#9070]/runnable@ForkJoinPool-1-worker-11)
虚拟线程 - 所有任务完成
总耗时: 170 毫秒
平均每个任务耗时: 0.02 毫秒

虚拟线程示例信息:
最后一个虚拟线程: Thread[#1,main,5,main]
是否为虚拟线程: false

Process finished with exit code 0

        可以看到,传统的线程受限于cpu核心数量,在相同的条件下。其平均耗时和总耗时远高于虚拟线程。在上面代码的基础上增加一个内存监视后

    private static void printMemoryUsage(String phase) {
        Runtime runtime = Runtime.getRuntime();
        long usedMemory = runtime.totalMemory() - runtime.freeMemory();
        System.out.printf("[内存使用] %s - 已用: %,d MB, 总量: %,d MB, 最大: %,d MB%n",
                phase,
                usedMemory / (1024 * 1024),
                runtime.totalMemory() / (1024 * 1024),
                runtime.maxMemory() / (1024 * 1024));
    }

        其改进后的结果如下:

开始线程性能对比测试
任务数量: 10000 | 每个任务处理时间: 100ms

===== 测试传统线程池 =====
线程池大小: 16
[内存使用] 线程池创建后 - 已用: 3 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 11/10000
[内存使用] 任务执行中 - 已用: 8 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 997/10000
[内存使用] 任务执行中 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 2,010/10000
[内存使用] 任务执行中 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 2,997/10000
[内存使用] 任务执行中 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 4,000/10000
[内存使用] 任务执行中 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 4,994/10000
[内存使用] 任务执行中 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 6,015/10000
[内存使用] 任务执行中 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 7,002/10000
[内存使用] 任务执行中 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 8,012/10000
[内存使用] 任务执行中 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 已完成任务: 9,006/10000
[内存使用] 任务执行中 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
传统线程池 - 所有任务完成
[内存使用] 任务完成后 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
总耗时: 69,092 毫秒
平均每个任务耗时: 6.91 毫秒

===== 测试虚拟线程 =====
[内存使用] 虚拟线程执行前 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
[内存使用] 虚拟线程执行器创建后 - 已用: 12 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 7/10000 (线程: VirtualThread[#46]/runnable@ForkJoinPool-1-worker-2)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 1,000/10000 (线程: VirtualThread[#1063]/runnable@ForkJoinPool-1-worker-6)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 2,001/10000 (线程: VirtualThread[#2063]/runnable@ForkJoinPool-1-worker-7)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 2,998/10000 (线程: VirtualThread[#3063]/runnable@ForkJoinPool-1-worker-2)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 3,998/10000 (线程: VirtualThread[#4070]/runnable@ForkJoinPool-1-worker-15)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 5,000/10000 (线程: VirtualThread[#5070]/runnable@ForkJoinPool-1-worker-5)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 5,999/10000 (线程: VirtualThread[#6070]/runnable@ForkJoinPool-1-worker-10)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 7,001/10000 (线程: VirtualThread[#7070]/runnable@ForkJoinPool-1-worker-15)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 8,000/10000 (线程: VirtualThread[#8070]/runnable@ForkJoinPool-1-worker-12)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 已完成任务: 8,999/10000 (线程: VirtualThread[#9070]/runnable@ForkJoinPool-1-worker-13)
[内存使用] 虚拟任务执行中 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
虚拟线程 - 所有任务完成
[内存使用] 虚拟任务完成后 - 已用: 24 MB, 总量: 244 MB, 最大: 3,904 MB
总耗时: 327 毫秒
平均每个任务耗时: 0.03 毫秒

虚拟线程示例信息:
最后一个虚拟线程: Thread[#1,main,5,main]
是否为虚拟线程: false

        可以看到,16个传统线程就已经花费了12mb,而10000个虚拟线程仅占用24m,比例达到了恐怖的1:625。

        虚拟线程由于其4大核心特性,具有强大的高并发处理能力,可以轻松支持百万级并发连接,特别适合现代高并发应用场景;其不仅支持现有的绝大部分生态,同时拥有JVM的自动资源管理能力,但是其在纯计算领域无法超越传统线程,还会因为调度开销导致性能下降,且其调试难度更高,且应该避免池化虚拟线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值