简介:虚拟线程常被误解为“不会阻塞”,更准确的理解是:阻塞仍会发生,但在支持卸载的调用路径上,挂起的是虚拟线程,平台线程可被释放去执行其他任务。本文从一次 socket 读取入手,对比平台线程与虚拟线程的调度差异,说明阻塞、卸载、继续执行之间的关系,并总结引入虚拟线程后需要重新审视的性能问题。
在 Java 21 引入虚拟线程之后,“虚拟线程不再有阻塞问题”成为常见说法。这个表述很容易让人误以为虚拟线程从根本上消除了阻塞,好像调用一个尚未返回的方法,后面的业务代码还能继续向下执行。事实上,只要代码是顺序执行的,调用没有返回,后续逻辑就必须等待。虚拟线程并没有改变 Java 语言的基本执行语义。
更准确的理解是:虚拟线程仍然会阻塞,但阻塞的成本模型发生了变化。对于普通平台线程来说,阻塞不仅意味着当前任务暂停,还意味着承载该任务的底层操作系统线程被占用。大量阻塞操作会导致平台线程资源迅速耗尽,系统无法调度更多就绪任务。而虚拟线程的设计目标之一,就是在发生可卸载的阻塞时,让底层平台线程不被无谓占用。
以一次常见的 socket 读取为例,调用方通过输入流读取网络数据。当数据尚未到达时,读取方法不会返回。使用传统平台线程执行这段代码时,会发生以下过程:
这个过程中,CPU 未必繁忙,但平台线程资源却在持续占用。如果系统中有大量这样的阻塞请求,即使很多任务只是在等待外部事件,平台线程也可能被占满。继续创建更多平台线程会带来内存、调度和上下文切换成本,因此传统模型中往往需要引入线程池、异步编程或响应式框架来规避平台线程的过度占用。
换成虚拟线程后,业务代码仍然使用相同的阻塞式读取写法,但运行行为发生了变化。当虚拟线程调用一个支持卸载的阻塞操作时,过程可能如下:
关键区别不在业务代码是否等待,而在于等待期间是否继续占用平台线程。虚拟线程从代码层面仍然是顺序阻塞的,但从资源调度层面看,虚拟线程的阻塞不再等同于平台线程的阻塞。
要准确理解虚拟线程,需要把“阻塞”和“卸载”区分开。
阻塞是业务语义层面的概念,表示当前调用尚未完成,后续代码不能继续执行。卸载是运行时调度层面的概念,表示虚拟线程在阻塞期间是否释放了底层平台线程。
并非所有阻塞都能自动完成卸载。例如,在同步锁内部执行阻塞式 I/O,或者某些没有为虚拟线程提供卸载支持的 native 调用,可能导致虚拟线程持有锁并阻塞,同时底层平台线程也被占用。因此,虚拟线程可以降低大量 I/O 密集场景下的资源占用,但不能简单认为所有阻塞都会被自动消除。
为了更直观地说明差异,可以用一个简单示意代码表示阻塞读取:
try (Socket socket = new Socket()) {socket.connect(new InetSocketAddress(host, port));InputStream input = socket.getInputStream();byte[] buffer = new byte[1024];// 数据未到达时,当前线程会一直等待int count = input.read(buffer);// 只有 read 返回后,后续逻辑才会执行process(buffer, count);}
在平台线程模型中,执行这段代码的线程在 read 等待期间被占用。在虚拟线程模型中,只要该阻塞点支持卸载,执行这段代码的虚拟线程虽然也在等待,但底层平台线程可以被释放出来处理其他虚拟线程。
这就意味着,同样是几万个并发连接,使用平台线程可能需要几万个操作系统线程;而使用虚拟线程时,只需要少量平台线程就可以承载大量处于等待状态的虚拟线程。
需要注意的是,虚拟线程并不会让等待本身消失,也不意味着 CPU 消耗会下降。如果一个任务本来就需要等待网络数据 100 毫秒,那么无论使用平台线程还是虚拟线程,业务结果的返回时间都不会缩短。
虚拟线程主要解决的是“等待期间大量线程资源被占用”的问题。它更适合 I/O 密集、并发量大、单个任务阻塞时间较长但计算量不大的场景。对于 CPU 密集型任务,虚拟线程并不能带来明显收益,因为这类任务本来就需要持续占用 CPU,平台线程必须始终处于运行状态。
引入虚拟线程后,开发人员仍然需要关注阻塞点是否支持卸载。例如,某些 synchronized 块中发生长期阻塞,可能继续占用平台线程。此时可以考虑使用支持更细粒度并发的锁,或者尝试缩小同步块范围。
另一方面,虚拟线程虽然降低了创建成本,但也不能无节制地创建。大量虚拟线程同时等待外部事件时,虽然平台线程负担不大,但每个虚拟线程仍然会占用一部分内存用于保存栈和状态信息。同时,大量任务被唤醒后,可能产生突发的 CPU 和内存压力。因此,在极高并发场景下,仍需要结合压测结果评估容量。
“Java 21 虚拟线程不再有阻塞问题”略过了关键条件。更准确的表述是:虚拟线程仍然会阻塞,业务代码仍然需要等待调用返回;但在支持卸载的阻塞路径上,虚拟线程的等待不再长时间占用平台线程。
这种变化带来的收益不是消灭等待,而是让底层的平台线程在虚拟线程等待时继续服务其他任务,从而显著提升系统的并发承载能力,尤其适合大量 I/O 等待场景。理解这一点后,开发人员可以更合理地使用虚拟线程,并继续关注锁竞争、卸载支持和容量压测等问题,而不是简单地把虚拟线程当作所有并发问题的万能替代方案。