Java21 虚拟线程在 synchronized 代码块中被 pin 住问题复现
Java juc JFR About 5,636 words测试代码
public class VirtualThreadSynchronizedPinnedDemo {
// 共享锁对象:所有虚拟线程会竞争这把锁
private static final Object LOCK = new Object();
// 倒计时器:确保所有虚拟线程同时开始竞争锁
private static final CountDownLatch LATCH = new CountDownLatch(1);
// 统计成功执行完 synchronized 块的虚拟线程数
private static int completedCount = 0;
public static void main(String[] args) throws Exception {
// 1. 配置:创建 100 个虚拟线程(远超默认载体线程数,模拟高并发)
Class<Thread> threadClass = Thread.class;
Method method = threadClass.getDeclaredMethod("currentCarrierThread");
method.setAccessible(true);
int virtualThreadCount = 100;
for (int i = 0; i < virtualThreadCount; i++) {
int threadId = i;
// 创建并启动虚拟线程
Thread.ofVirtual().name("VT-" + threadId).start(() -> {
try {
// 等待所有虚拟线程就绪,统一开始竞争锁
LATCH.await();
// 关键:进入 synchronized 块——Java 21 中会导致虚拟线程被固定
synchronized (LOCK) {
System.out.printf(LocalDateTime.now() + " [%s] [%s] 成功获取锁,开始执行(此时虚拟线程被固定)%n",
method.invoke(null), Thread.currentThread().getName());
// 模拟业务阻塞(如数据库查询、IO 操作)
Thread.sleep(1000); // 阻塞期间,虚拟线程无法卸载,载体线程被占用
// 执行完成,计数+1
completedCount++;
System.out.printf(LocalDateTime.now() + " [%s] [%s] 释放锁,执行完成%n", method.invoke(null), Thread.currentThread().getName());
}
} catch (Exception e) {
Thread.currentThread().interrupt();
System.out.printf(LocalDateTime.now() + " [%s] 被中断%n", Thread.currentThread().getName());
}
});
}
new Thread(() -> {
for (long i = 0; i < Long.MAX_VALUE; i++) {
Thread.ofVirtual().start(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
});
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}).start();
System.out.println(LocalDateTime.now() + " 所有虚拟线程就绪,开始竞争锁...");
LATCH.countDown();
}
}
输出
2025-10-05T13:27:48.199150300 [Thread[#124,ForkJoinPool-1-worker-4,5,CarrierThreads]] [VT-0] 成功获取锁,开始执行(此时虚拟线程被固定)
2025-10-05T13:27:49.243506500 [Thread[#124,ForkJoinPool-1-worker-4,5,CarrierThreads]] [VT-0] 释放锁,执行完成
2025-10-05T13:27:49.243506500 [Thread[#22,ForkJoinPool-1-worker-1,5,CarrierThreads]] [VT-3] 成功获取锁,开始执行(此时虚拟线程被固定)
2025-10-05T13:27:50.248552900 [Thread[#22,ForkJoinPool-1-worker-1,5,CarrierThreads]] [VT-3] 释放锁,执行完成
2025-10-05T13:27:50.249250100 [Thread[#33,ForkJoinPool-1-worker-2,5,CarrierThreads]] [VT-4] 成功获取锁,开始执行(此时虚拟线程被固定)
2025-10-05T13:27:51.262913500 [Thread[#33,ForkJoinPool-1-worker-2,5,CarrierThreads]] [VT-4] 释放锁,执行完成
2025-10-05T13:27:51.263927 [Thread[#48,ForkJoinPool-1-worker-3,5,CarrierThreads]] [VT-1] 成功获取锁,开始执行(此时虚拟线程被固定)
2025-10-05T13:27:52.271131500 [Thread[#48,ForkJoinPool-1-worker-3,5,CarrierThreads]] [VT-1] 释放锁,执行完成
2025-10-05T13:27:52.271854400 [Thread[#33,ForkJoinPool-1-worker-2,5,CarrierThreads]] [VT-7] 成功获取锁,开始执行(此时虚拟线程被固定)
2025-10-05T13:27:53.274071200 [Thread[#33,ForkJoinPool-1-worker-2,5,CarrierThreads]] [VT-7] 释放锁,执行完成
2025-10-05T13:27:53.275088900 [Thread[#22,ForkJoinPool-1-worker-1,5,CarrierThreads]] [VT-6] 成功获取锁,开始执行(此时虚拟线程被固定)
2025-10-05T13:27:54.290074 [Thread[#22,ForkJoinPool-1-worker-1,5,CarrierThreads]] [VT-6] 释放锁,执行完成
2025-10-05T13:27:54.291091300 [Thread[#124,ForkJoinPool-1-worker-4,5,CarrierThreads]] [VT-5] 成功获取锁,开始执行(此时虚拟线程被固定)
2025-10-05T13:27:55.305432100 [Thread[#124,ForkJoinPool-1-worker-4,5,CarrierThreads]] [VT-5] 释放锁,执行完成
2025-10-05T13:27:55.306165 [Thread[#22,ForkJoinPool-1-worker-1,5,CarrierThreads]] [VT-10] 成功获取锁,开始执行(此时虚拟线程被固定)
2025-10-05T13:27:56.314520400 [Thread[#22,ForkJoinPool-1-worker-1,5,CarrierThreads]] [VT-10] 释放锁,执行完成
2025-10-05T13:27:56.315567900 [Thread[#33,ForkJoinPool-1-worker-2,5,CarrierThreads]] [VT-9] 成功获取锁,开始执行(此时虚拟线程被固定)
2025-10-05T13:27:57.321925100 [Thread[#33,ForkJoinPool-1-worker-2,5,CarrierThreads]] [VT-9] 释放锁,执行完成
现象
可以看到日志输出中,获取锁后sleep了一秒,但一秒后虚拟线程的载体线程还是原来的平台线程,并没有挂载到其他平台线程,所以猜测虚拟线程根本没有从平台线程上卸载。
下文使用JFR验证。
VirtualThreadPinned 事件
使用JFR查看jdk.VirtualThreadPinned事件。
启动参数
添加启动参数。
java -XX:StartFlightRecording=disk=true,dumponexit=true -jar test.jar
分析 JFR 文件
使用jfr命令打印指定事件。
jfr print --events jdk.VirtualThreadPinned hotspot-pid-13500-id-1-2025_10_05_15_06_02.jfr
jdk.VirtualThreadPinned {
startTime = 15:05:46.101 (2025-10-05)
duration = 1.04 s
eventThread = "VT-1" (javaThreadId = 30, virtual)
stackTrace = [
java.lang.VirtualThread.parkOnCarrierThread(boolean, long) line: 675
java.lang.VirtualThread.parkNanos(long) line: 634
java.lang.VirtualThread.sleepNanos(long) line: 791
java.lang.Thread.sleep(long) line: 507
com.example.VirtualThreadSynchronizedPinnedDemo.lambda$main$0(Method) line: 47
...
]
}
jdk.VirtualThreadPinned {
startTime = 15:05:47.145 (2025-10-05)
duration = 1.05 s
eventThread = "VT-3" (javaThreadId = 32, virtual)
stackTrace = [
java.lang.VirtualThread.parkOnCarrierThread(boolean, long) line: 675
java.lang.VirtualThread.parkNanos(long) line: 634
java.lang.VirtualThread.sleepNanos(long) line: 791
java.lang.Thread.sleep(long) line: 507
com.example.VirtualThreadSynchronizedPinnedDemo.lambda$main$0(Method) line: 47
...
]
}
Views: 4 · Posted: 2025-12-24
———         Thanks for Reading         ———
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓
Loading...