Java并发性的一种新方法 虚构线程简介
作者 | Matthew Tyson
译者 | 李睿
Java19影响最深远的更新之一是引入了虚构线程。虚构线程是Project Loom的一局部,可以在Java19预览版中经常使用。
虚构线程如何上班
虚构线程在操作系统进程和运行程序级并发之间引入了一个形象层。换句话说,虚构线程可用于调度Java虚构机编排的义务,因此JVM在操作系统和程序之间起到中介作用。图1展现了虚构线程的架构。
图1.Java中虚构线程的架构
在这种架构中,运行程序实例化虚构线程,并由JVM调配处置虚构线程的计算资源。与此相比,惯例线程间接映射到操作系统(OS)进程。关于惯例线程,运行程序代码担任提供和调配操作系统资源。而经常使用虚构线程,运行程序可以实例化虚构线程,从而表白并发性的需求。但正是JVM从操作系统失掉和监禁资源。
Java中的虚构线程相似于Go言语中的goroutine。在经常使用虚构线程时,JVM只能在运行程序的虚构线程被驻留时调配计算资源,这象征着它们处于闲暇形态并期待新的事情。这种闲暇在大少数主机中是经常出现的:它们将一个线程调配给一个恳求,而后处于闲暇形态,并期待一个新的事情,例如来自数据存储的照应或来自网络的进一步输入。
经常使用传统Java线程,当主机在处置恳求时处于闲暇形态时,操作系统线程也处于闲暇形态,这重大限度了主机的可裁减性。正如NicolaiParlog所解释的那样,“操作系统不可提高平台线程的效率,但JDK经过切断其线程与操作系统线程之间的一对一相关,可以更好地利用它们。”
以前为缓解与传统Java线程相关的性能和可裁减性疑问所做的致力包括异步、照应式库(如JavaRX)。虚构线程的不同之处在于它们是在JVM级别成功的,但是它们适宜Java中现有的编程结构。
经常使用Java虚构线程:演示
在这个演示中,创立了一个经常使用Maven原型的便捷Java运行程序。为此还做了一些更改,以便在Java19预览版中启用虚构线程。一旦虚构线程被更新到预览之外,就不须要做这些更改了。
清单1显示了对Maven原型的POM文件所做的更改。须要留意的是,还将编译器设置为经常使用Java19,并在.mvn/jvm.config中增加了一行(例如清单2所示)。
清单1.演示运行程序的pom.xml
<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>19</maven.compiler.source><maven.compiler.target>19</maven.compiler.target></properties><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.10.1</version><configuration><compilerArgs><arg>--add-modules=jdk.incubator.concurrent</arg><arg>--enable-preview</arg></compilerArgs></configuration></plugin>
要使exec:java在启用预览的状况下上班,必定经常使用enable-preview开关。它经常使用所需的开关启动Maven进程。
清单2.将enable preview增加到.mvn/jvm.config
--enable-preview
如今,可以经常使用mvn compile exec:java口头该程序,虚构线程个性将被编译和口头。
经常使用虚构线程的两种方法
如今思考在代码中实践经常使用虚构线程的两种关键模式。虽然虚构线程对JVM的上班模式发生了渺小的变动,但其代码实践上与传统Java线程十分相似。设计上的相似性使得重构现有的运行程序和主机相对容易。这种兼容性还象征着用于监督和观察JVM中的线程的现有工具将与虚构线程一同上班。
Thread.startVirtualThread(Runnable r)
经常使用虚构线程的最基本方法是经常使用Thread.startVirtualThread(Runnabler))。这是实例化线程和调用thread.start()的代替方法。检查清单3中的示例代码。
清单3.实例化一个新线程
package com.infoworld;import java.util.Random;public class App {public static void main( String[] args ) {boolean vThreads = args.length > 0;System.out.println( "Using vThreads: " + vThreads);long start = System.currentTimeMillis();Random random = new Random();Runnable runnable = () -> { double i = random.nextDouble(1000) % random.nextDouble(1000);};for (int i = 0; i < 50000; i++){if (vThreads){Thread.startVirtualThread(runnable);} else {Thread t = new Thread(runnable);t.start();}}long finish = System.currentTimeMillis();long timeElapsed = finish - start;System.out.println("Run time: " + timeElapsed);}}
当带有参数运转时,清单3中的代码将经常使用一个虚构线程,否则将经常使用惯例线程。无论选用哪种线程类型,该程序都会生成5万次迭代。而后,它用随机数做一些便捷的数学运算,并跟踪口头所需的期间。
要经常使用虚构线程运转代码,须要键入:mvn-compile-exec:java-Dexec.args=“true”。要经常使用规范线程运转,须要键入:mvn-compile-exec:java。为此启动了一个极速的性能测试,失掉如下结果:
这些结果是不迷信的,但是运转时的差异是渺小的。
还有其余经常使用Thread生成虚构线程的方法,例如Thread.ofVirtual().start(runnable)。
经常使用口头器
启动虚构线程的另一种关键方法是经常使用口头器。口头器在处置线程时很经常出现,它提供了一种协调许多义务和线程池的规范方法。
虚构线程不须要经常使用线程池,由于创立和处置它们的老本很低,因此没有必要经常使用线程池。与其同样,可以将JVM看作是治理线程池。但是,许多程序确实经常使用口头器,因此Java19在口头器中蕴含了一个新的预览方法,使重构虚构线程变得容易。清单4展现了新方法和旧方法。
清单4.新的口头器方法
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); // New methodExecutorService executor = Executors.newFixedThreadPool(Integer poolSize); // Old method
左右滑动检查完整代码
此外,Java19引入了Executors.newThreadPerTaskExecutor(ThreadFactorythreadFactory)方法,它可以驳回构建虚构线程的ThreadFactory。这样的线程工厂可以经过Thread.ofVirtual().factory().取得。
虚构线程的低劣通常
普通来说,由于虚构线程成功了线程类,所以它们可以在规范线程所在的任何中央经常使用。但是,在如何经常使用虚构线程以取得最佳成果方面存在差异。一个例子是在访问数据存储等资源时经常使用信号量来控制线程数量,而不是经常使用有限度的线程池。
另一个关键留意事项是,虚构线程一直守护线程,这象征着它们将使蕴含它们的JVM进程坚持优惠形态,直到它们成功。此外,不能更改它们的优先级。更改优先级和守护进程形态的方法为无操作(no-ops)。
经常使用虚构线程重构
虚构线程在实质上是一个很大的扭转,但它们很容易运行到现有的代码库中。虚构线程将对Tomcat和GlassFish等主机发生最大、最间接的影响。这样的主机应该能够以最小的致力驳回虚构线程。在这些主机上运转的运行程序将取得可裁减性的收益,而无需对代码启动任何更改,这或者对大规模运行程序发生渺小影响。思考一个运转在多个主机和外围上的Java运行程序,突然之间它将能够处置一个数量级的并发恳求,当然这齐全取决于恳求处感性能文件。
像Tomcat这样的主机准许带性能参数的虚构线程或者只是期间疑问。与此同时,假设对将主机迁徙到虚构线程感到猎奇,可以浏览CayHorstmann撰写的一篇博客文章,他在文章中展现了为虚构线程性能Tomcat的环节。他启用了虚构线程预览性能,并将Executor交流为只差一行的自定义成功。可裁减性的好处是清楚的,正如他在文章中所说:“经过这种更改,200个恳求只有3秒,而Tomcat可以轻松处置10,000个恳求。”
论断
虚构线程是JVM的一个关键变动。关于运行程序程序员来说,它们代表了异步格调编码(如经常使用回调)的另一种选用。总之,在处置Java并发性时,可以将虚构线程看作是一个摆向Java中同步编程范式的钟摆。这在编程格调上大抵相似于JavaScript引入的async/await(虽然在成功上齐全不同)。简而言之,经常使用便捷的同步语法编写正确的异步行为变得相当容易,至少在线程破费少量期间闲暇的运行程序中是这样。
原文链接: