JDK21虚拟线程缺陷总结

一、前言

JDK21新增了“虚拟线程”的新特性,但在实际探索过程中,发现虚拟线程目前还不够成熟,存在一些缺陷( 官方文档:JEP 444: Virtual Threads)。本文列举了这些缺陷,并提供了可以触发这些缺陷的示例。

二、缺陷及示例

1、使用虚拟线程后无法通过池化的方式、配置最大线程数来控制并发数,但可以通过信号量等方法来专门解决这个问题

2、JDK中的一些阻塞操作不会卸载虚拟线程,从而阻塞平台线程和底层OS线程,导致载体线程暂时扩容、产生性能开销,示例如下:

public class CarrierThreadExpandTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
        File file = new File("/Users/info.log");
        for (int i = 0; i < 32; i++) {
            executorService.execute(() -> {
                while (true) {
                    try (FileInputStream fis = new FileInputStream(file)) {
                        int content;
                        while ((content = fis.read()) != -1) {
                            System.out.print((char) content);
                        }
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }

                }
            });
        }
        Thread.sleep(1000 * 60 * 60);
    }
}

通过jstack pid 指令查看"ForkJoinPool-1-worker-xx”线程即为平台线程,可以看到发生了扩容。

3、在 synchronized 块或方法中执行代码、执行 native 方法或外部函数时,虚拟线程发生阻塞不仅无法被卸载(pinned),载体线程也不会扩容;会导致其他虚拟线程得不到调度,示例如下:

public class VirtualThreadUnmountTest {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
        for (int i = 0; i < 8; i++) {
            executorService.execute(VirtualThreadUnmountTest::doSth);
        }
        long[] nums = new long[10];
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            executorService.execute(() -> {
                while (true) {
                    nums[finalI]++;
                }
            });
        }
        Thread.sleep(1000 * 15);
        System.out.println(Arrays.toString(nums));
    }

    public synchronized static void doSth() {
        try {
            Thread.sleep(1000 * 60 * 60);
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
    }

}

// 输出:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

4、虚拟线程暂未实现基于时间分片的抢占式调度(对于传统的平台线程,这部分工作由 OS 负责完成);如果部分任务不阻塞地持续运行,就会持续占用CPU,影响其他任务执行(正常情况下我们的业务线程应该不会出现持续运行的情况),示例如下:

public class VirtualThreadHoldCpuTest {
    public static void main(String[] args) throws InterruptedException {
      	// newVirtualThreadPerTaskExecutor,为每一个请求创建一个虚拟线程
        ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
        long[] nums = new long[100];
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            executorService.execute(() -> {
              	// 死循环持续运行
                while (true) {
                    nums[finalI]++;
                }
            });
        }
        Thread.sleep(1000 * 30);
        System.out.println(Arrays.toString(nums));
    }
}

// 输出:[7021009361, 6579258416, 6695810714, 6931415415, 6924053995, 6984066224, 6900750367, 6839905353, 0, 0, 0, 0, 0, 0, 0, 0, 
//    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
//    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

5、结构化并发特性中断子任务是通过调用 Thread.interrupt 方法实现的,sleep、wait方法可以自动响应该中断;但是我们自己的业务代码可能会无法自动响应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值