You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JDK21中虚拟线程引发类死锁问题:代码执行异常冻结

虚拟线程下同步锁导致冻结的原因分析

先看问题中的测试代码:

public class Test {

    private static final Object lock = new Object();
    
    public static void main(String[] args) {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 100; i++) {
                int taskId = i;
                executor.submit(() -> performTask(taskId));
            }
        }
        System.out.println("All tasks completed.");
    }
    
    private static void performTask(int id) {
        System.out.printf("Task %d started. Thread: %s%n", id, Thread.currentThread());
        synchronized (lock) {
            System.out.printf("Task %d acquired lock. Thread: %s%n", id, Thread.currentThread());
            try {
                Thread.sleep(Duration.ofSeconds(2));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.printf("Task %d finished. Thread: %s%n", id, Thread.currentThread());
    }
}

为什么会出现冻结?

核心问题出在虚拟线程的调度机制和Thread.sleep的行为上:

  1. Thread.sleep不会释放平台线程
    虚拟线程的优势是执行可挂载操作时会释放绑定的平台线程,让其他虚拟线程复用。但Thread.sleep()是例外——它会直接让虚拟线程绑定的平台线程进入睡眠状态,不会释放这个平台线程。

  2. 平台线程池资源耗尽
    Executors.newVirtualThreadPerTaskExecutor()默认使用的平台线程池大小等于CPU可用处理器数(比如4核机器就是4个平台线程)。提交100个虚拟线程任务后:

    • 前4个虚拟线程抢占到平台线程,其中一个先拿到synchronized锁,调用Thread.sleep(2s)后占住平台线程。
    • 剩下3个虚拟线程尝试拿锁失败,进入阻塞队列,但synchronized阻塞属于不可挂载阻塞,它们会一直占着平台线程等待锁。
    • 此时4个平台线程全被占满:1个持有锁sleep,3个等待锁阻塞。剩下的96个虚拟线程没有平台线程可用,无法尝试获取锁。
    • 当第一个sleep的虚拟线程醒来释放锁后,它的平台线程会唤醒一个等待锁的虚拟线程,但新拿到锁的虚拟线程又会调用Thread.sleep继续占住平台线程。循环往复,所有平台线程都卡在sleep或等待锁状态,整个executor彻底卡住,表现为类似死锁的冻结。

关键区分:虚拟线程的可挂载与不可挂载阻塞

虚拟线程只有在执行可挂载操作时才会释放平台线程,比如:

  • Object.wait()
  • LockSupport.park()
  • Java 19+支持的NIO阻塞操作
  • 异步API(如CompletableFuture)

而synchronized阻塞、Thread.sleep()都属于不可挂载的阻塞,会持续占用平台线程。

修复方案

把Thread.sleep(Duration.ofSeconds(2))换成虚拟线程可挂载的睡眠操作,比如用LockSupport.parkNanos:

// 替换原sleep代码
LockSupport.parkNanos(Duration.ofSeconds(2).toNanos());

这样虚拟线程在睡眠时会释放平台线程,其他等待锁的虚拟线程就能拿到平台线程尝试获取锁,代码会按预期串行执行。

内容的提问来源于stack exchange,提问作者Strayer_xx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 00:34:50