JEP444中虚拟线程何时扩容底层载体线程?求代码示例
虚拟线程扩容底层载体线程的场景与代码示例
根据JEP 444的描述,虚拟线程的调度器(基于ForkJoinPool)会在虚拟线程导致底层载体OS线程被阻塞且无法被JVM调度机制接管的场景下,临时扩容并行度来补偿。具体场景及代码示例如下:
1. 同步IO阻塞场景
当虚拟线程执行java.io包下的同步IO操作(如文件读取、Socket同步通信)时,这些操作会直接阻塞底层OS线程,虚拟线程无法被JVM调度器主动切换。此时调度器会临时扩展并行度,新增载体线程来处理其他等待的虚拟线程。
import java.io.FileInputStream; import java.io.IOException; import java.util.concurrent.Executors; public class VirtualThreadIOBlock { public static void main(String[] args) throws InterruptedException { try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { // 提交100个执行同步文件读取的虚拟线程 for (int i = 0; i < 100; i++) { executor.submit(() -> { try (var fis = new FileInputStream("large-file.txt")) { byte[] buffer = new byte[1024]; // 阻塞式读取文件,占用载体线程 while (fis.read(buffer) != -1) { // 模拟数据处理 } } catch (IOException e) { e.printStackTrace(); } return null; }); } // 等待所有任务完成 Thread.sleep(10000); } } }
说明:多个虚拟线程同时执行同步IO时,每个阻塞的IO操作会占用一个载体线程,调度器自动扩容载体线程数量,避免其他虚拟线程长时间等待。
2. 持有synchronized锁时的阻塞场景
synchronized是基于OS线程的监视器锁,虚拟线程进入synchronized块/方法后会被固定到当前载体线程,直到退出同步块。如果此时该载体线程因同步IO、JNI调用等操作被阻塞,调度器会临时扩容并行度来补偿。
import java.io.FileInputStream; import java.io.IOException; import java.util.concurrent.Executors; public class VirtualThreadSyncBlock { private static final Object LOCK = new Object(); public static void main(String[] args) throws InterruptedException { try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 100; i++) { executor.submit(() -> { synchronized (LOCK) { try (var fis = new FileInputStream("large-file.txt")) { byte[] buffer = new byte[1024]; // 同步IO阻塞,此时载体线程被占用 while (fis.read(buffer) != -1) {} } catch (IOException e) { e.printStackTrace(); } } return null; }); } Thread.sleep(10000); } } }
说明:虚拟线程在
synchronized块内执行阻塞操作时,载体线程被固定且无法处理其他虚拟线程,调度器会新增载体线程维持整体执行效率。
3. JNI/JNA阻塞调用场景
如果虚拟线程调用了阻塞的JNI/JNA方法,底层载体线程会被本地代码阻塞,JVM无法通过虚拟线程调度机制切换该载体线程,因此调度器会临时扩容并行度,分配新的载体线程。
import java.util.concurrent.Executors; public class VirtualThreadJNI { // 加载包含阻塞JNI方法的本地库 static { System.loadLibrary("blocking-native-lib"); } // 声明阻塞的JNI方法 private native void blockingNativeCall(); public static void main(String[] args) throws InterruptedException { try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 50; i++) { executor.submit(() -> { // 调用阻塞的JNI方法,占用载体线程 new VirtualThreadJNI().blockingNativeCall(); return null; }); } Thread.sleep(15000); } } }
说明:JNI调用会绕过JVM的虚拟线程调度,阻塞载体线程,调度器通过扩容保证其他虚拟线程的执行不受影响。
内容的提问来源于stack exchange,提问作者FlyChenKai
相关产品推荐
相关产品推荐

