普通多线程 VS 虚拟线程

1、介绍

虚拟线程(Virtual Threads)和普通线程(Platform Threads)的基本概念。根据知识库,虚拟线程是JDK 21引入的,由JVM管理,而普通线程是操作系统级别的。需要解释它们的管理方式,比如虚拟线程在用户空间,而普通线程在内核空间。

虚拟线程更轻量,可以创建更多数量,适合高并发场景。而普通线程因为每个线程对应一个OS线程,资源消耗大,数量有限。特别是处理IO密集型任务时虚拟线程的优势。

2、比较

2.1 案例-1

    @Test
    public void test14(){
        long start = System.currentTimeMillis();
        try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()){
            IntStream.range(0, 1000000).forEach(i -> executorService.submit(() -> {
                String aa = "xx";
            }));
        }catch (Exception e){
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        System.out.println("虚拟线程耗时:"+(end-start));
    }


    @Test
    public void test15(){
        long start = System.currentTimeMillis();
        try (ExecutorService executorService = Executors.newFixedThreadPool(10)){
            IntStream.range(0, 1000000).forEach(i -> executorService.submit(() -> {
                String aa = "xx";
            }));
        }catch (Exception e){
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        System.out.println("普通耗时:"+(end-start));
    }

结果:
(1)第一次执行
与预期一样
在这里插入图片描述
(2)第二次执行
与预期不一样
在这里插入图片描述

为什么会出现这种情况????
原因:

在运行上述代码时,有时会出现普通线程(newFixedThreadPool)执行比虚拟线程(newVirtualThreadPerTaskExecutor)快>的情况,这主要是由以下几个原因导致的:

(1) 虚拟线程的创建开销

虽然虚拟线程的创建和销毁成本相对较低,但在初始阶段,虚拟线程的创建过程仍然存在一定的开销。当任务数量较少或者任务本身非常简单时,创建虚拟线程的开销可能会成为性能瓶颈。在你的代码中,每个任务只是简单地创建一个字符串变量,任务本身的执行时间极短,此时虚拟线程的创建开销可能会对整体性能产生较大影响。

(2) 线程调度开销

虚拟线程是基于载体线程(通常是平台线程)进行调度的,线程调度需要一定的时间和资源。当任务简单且数量有限时,频繁的线程调度可能会导致性能下降。而newFixedThreadPool使用的是固定数量的平台线程,线程调度相对简单,开销较小。
(3)缓存局部性

平台线程(普通线程)通常在硬件层面有更好的缓存局部性。当线程在执行任务时,会将数据加载到 CPU
缓存中,以便更快地访问。由于平台线程是直接与操作系统内核交互的,它们在执行过程中更有可能利用 CPU
缓存,从而提高性能。而虚拟线程在调度过程中可能会频繁切换载体线程,导致缓存局部性变差。
(4)测试环境的不确定性

测试环境中的各种因素,如 CPU
负载、内存使用情况、操作系统的调度策略等,都会对测试结果产生影响。在不同的时间点运行测试代码,系统的状态可能会有所不同,从而导致测试结果的波动。

那我们又该如何使用才能体现虚拟线程的优势?

2.2 案例-2

 @Test
    public void test19() throws InterruptedException {
        // 测试普通线程
        testNormalThreads();
        // 测试虚拟线程
        testVirtualThreads();
    }

    private void testNormalThreads() throws InterruptedException {
        long startTime = System.currentTimeMillis();
        // 创建一个固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(100);

        IntStream.range(0, REQUEST_COUNT).forEach(i -> {
            executorService.submit(() -> {
                try {
                    sendHttpRequest();
                } catch (IOException | InterruptedException e) {
                    e.printStackTrace();
                }
            });
        });

        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        long endTime = System.currentTimeMillis();
        System.out.println("普通线程执行 " + REQUEST_COUNT + " 个请求耗时: " + (endTime - startTime) + " 毫秒");
    }

    private  void testVirtualThreads() throws InterruptedException {
        long startTime = System.currentTimeMillis();
        // 创建一个为每个任务创建一个虚拟线程的执行器
        try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, REQUEST_COUNT).forEach(i -> {
                executorService.submit(() -> {
                    try {
                        sendHttpRequest();
                    } catch (IOException | InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            });
        }
        long endTime = System.currentTimeMillis();
        System.out.println("虚拟线程执行 " + REQUEST_COUNT + " 个请求耗时: " + (endTime - startTime) + " 毫秒");
    }

    private void sendHttpRequest() throws IOException, InterruptedException {
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(URL))
                .GET()
                .build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    }

结果:
(1) 第一次执行
在这里插入图片描述
(2)第二次执行
在这里插入图片描述
多次执行明显虚拟线程会比普通线程要执行快,性能好!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值