• Java21的虚拟线程Virtual Thread初体验


    我们之前使用的是操作系统平台的线程,就称之为“系统线程”吧。虚拟线程是JDK维护的,原理跟WebFlux的底层实现差不多,都是工作线程分离。

    要使用虚拟线程,需要使用JDK21以上,包括21。

    虚拟线程可以创建很多很多

    系统线程不能轻易创建太多,我们一直被教导创建线程是很重的活动。

            for (int i = 0; i < 1_000_000; i++) {
                new Thread(() -> {
                    longAdder.increment();
                    System.out.println(longAdder.longValue());
                    try {
                        Thread.sleep(10000);
                    } catch (Exception e) {
                        // deal with e
                    }
                }).start();
            }
    

    上面尝试创建百万个线程,线程都会休眠不结束。我用了一个LongAdder记录我的笔记本能实际创建多少线程。结果是4000多个,用了6秒:
    image
    改成虚拟线程就轻松成功:

            LongAdder longAdder = new LongAdder();
            for (int i = 0; i < 1_000_000; i++) {
                Thread.ofVirtual().start(() -> {
                    longAdder.increment();
                    System.out.println(longAdder.longValue());
                    try {
                        Thread.sleep(100000);
                    } catch (Exception e) {
                        // deal with e
                    }
                });
            }
    

    因为虚拟线程很轻量,所以不要使用线程池,可以很轻易的创建很多个。因为能创建很多,所以也不要使用 Thread Local 变量。

    IO操作不好阻塞虚拟线程的使用

    使用系统线程,必须通过线程池来处理多个任务,不然问题很严重:

        static void callService(String taskName) {
            try {
                System.out.println(Thread.currentThread() + " executing " + taskName);
                new URL("自己写一个http接口?sleep=2000").getContent();
                System.out.println(Thread.currentThread() + " completed " + taskName);
    
            } catch (Exception e) {
                // deal with e
            }
        }
    	
    try (ExecutorService executor = Executors.newFixedThreadPool(5)) {
    	for (int i = 0; i <= 10; i++) {
    		String taskName = "Task" + i;
    		executor.execute(() -> callService(taskName));
    	}
    }
    

    执行的时候你能看到,线程在执行结束前需要空闲等待任务的IO。毕竟每个任务都是在某一个线程上执行 —— 说这个干啥?
    看一下虚拟线程

            try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
                for (int i = 0; i <= 600; i++) {
                    String taskName = "Task" + i;
                    executor.execute(() -> callService(taskName));
                }
            }
    

    这里创建了一个虚拟线程工厂(而不是线程池,记住不要使用虚拟化的线程池),它会给每个任务创建新的虚拟线程。
    程序启动后,会立即打印600个"executing",而不是像系统线程那样只打印5个。
    为了方便,我们少用几个任务来实验看一下输出:

    VirtualThread[#50]/runnable@ForkJoinPool-1-worker-2 executing Task1
    VirtualThread[#48]/runnable@ForkJoinPool-1-worker-1 executing Task0
    VirtualThread[#51]/runnable@ForkJoinPool-1-worker-3 executing Task2
    VirtualThread[#51]/runnable@ForkJoinPool-1-worker-2 completed Task2
    VirtualThread[#48]/runnable@ForkJoinPool-1-worker-3 completed Task0
    VirtualThread[#50]/runnable@ForkJoinPool-1-worker-1 completed Task1
    

    仔细看,这里一共3个虚拟线程,因为工厂创建了三个,根据任务数来的。
    但是每个任务都是在两个虚拟线程上:Task1 被worker-2接收,却被worker-1完成。

    啥时候用

    关键问题来了,我们总该使用虚拟线程吗?
    对各种问题都通用的答案是:你没遇到问题就别想着解决问题。
    如果的确有问题,想看看虚拟线程是否合适,可以看一下任务是否是IO密集型的。
    对于计算密集型任务,系统线程比虚拟线程有效得多。
    虚拟线程跟WebFlux一样,只能提升系统的吞吐量,并不能加快单个任务的完成时间。

  • 相关阅读:
    ruoyi前后端分离3.8.6版本,把用户昵称设置到Vuex中,使任何组件都能直接获取用户的昵称
    什么是上拉,下拉?
    EasyNLP 开源中文 NLP 算法框架
    卡码网语言基础课 |出现频率最高的字母
    VR全景智慧文旅解决方案,助力文旅产业转型升级
    ABAP 设置开票后不允许修改采购订单价格
    Java集合框架:Collection 与 Map 接口深度解析
    chromadb 0.4.0 后的改动
    总结下.NET后端已经熟悉或者使用过了这么多东西,有没你喜欢用的
    C复习-输入输出函数+流
  • 原文地址:https://www.cnblogs.com/somefuture/p/18307345