前提 趁着国庆前后阅读了虚拟线程相关的源码,写了一篇《虚拟线程VirtualThread源码透视》,里面介绍了虚拟线程的实现原理和使用示例。需要准备做一下前期准备:安装OpenJDK19或者OracleJDK19准备好嵌入式Tomcat的依赖,需要引入三个依赖包,分别是tomcatembedcore、tomcatembedel和tomcatembedwebsocket,版本选用10。1。0 查看Tomcat官方文档的CHANGELOG: tomcatvirtualthread1 支持Loom项目的Tomcat最低版本为10。1。0M16,对应的正式版是10。1。0(当前时间为20221007前后),低于此版本因为大量API还没有适配虚拟线程,主要是没有改造监视器锁的引用导致虚拟线程pin到载体(平台)线程等问题,因此别无他选。另外,重要的提醒说三次:本文是实验性质,在未完全证实改造功能可以应用生产环境前需要谨慎评估,或者先别使用于生产环境本文是实验性质,在未完全证实改造功能可以应用生产环境前需要谨慎评估,或者先别使用于生产环境本文是实验性质,在未完全证实改造功能可以应用生产环境前需要谨慎评估,或者先别使用于生产环境引入依赖 引入以下依赖:dependencygroupIdorg。apache。tomcat。embedgroupIdtomcatembedcoreartifactIdversion10。1。0versiondependencydependencygroupIdorg。apache。tomcat。embedgroupIdtomcatembedelartifactIdversion10。1。0versiondependencydependencygroupIdorg。apache。tomcat。embedgroupIdtomcatembedwebsocketartifactIdversion10。1。0versiondependency编程式初始化Tomcat 为了使用反射调用一些java。base模块下没开放的依赖包和跟踪虚拟线程栈,程序运行时候加入下面的VM参数:addopensjava。basejava。langALLUNNAMEDaddopensjava。basejava。lang。reflectALLUNNAMEDaddopensjava。basejava。util。concurrentALLUNNAMEDDjdk。tracePinnedThreadsfull 在IDEA的运行配置中是这个样子: tomcatvirtualthread2 接着编写一个HttpServlet实现:publicclassVirtualThreadHandleServletextendsHttpServlet{privatestaticfinalDateTimeFormatterFORMATTERDateTimeFormatter。ofPattern(yyyyMMddHH:mm:ss。SSS);Overrideprotectedvoidservice(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{ThreadthreadThread。currentThread();System。out。printf(servicebythreads,isvirtuals,carrierthreads,thread。getName(),thread。isVirtual(),getCurrentCarrierThreadName(thread));resp。setStatus(HttpServletResponse。SCOK);resp。setHeader(ContentType,applicationjson);Stringcontent{time:LocalDateTime。now()。format(FORMATTER)};resp。getWriter()。write(content);}privatestaticStringgetCurrentCarrierThreadName(ThreadcurrentThread){if(currentThread。isVirtual()){try{MethodHandlemethodHandleMethodHandles。privateLookupIn(Thread。class,MethodHandles。lookup())。findStatic(Thread。class,currentCarrierThread,MethodType。methodType(Thread。class));ThreadcarrierThread(Thread)methodHandle。invoke();returncarrierThread。getName();}catch(Throwablee){e。printStackTrace();}}returnUNKNOWN;}} 该Servlet实现比较简单,就是在控制台打印一些虚拟线程和载体线程的一些信息,然后返回HTTP状态码为200和一个JSON字符展示当前精确到毫秒的时间。接着编写一个main方法初始化Tomcat:publicclassEmbedTomcatVirtualThreadDemo{privatestaticfinalStringSERVLETNAMEVirtualThreadHandleServlet;privatestaticfinalStringSERVLETPATH;设置VM参数:addopensjava。basejava。langALLUNNAMEDaddopensjava。basejava。lang。reflectALLUNNAMEDaddopensjava。basejava。util。concurrentALLUNNAMEDDjdk。tracePinnedThreadsfullparamargsargsthrowsExceptionepublicstaticvoidmain(String〔〕args)throwsThrowable{StringpinModeSystem。getProperty(jdk。tracePinnedThreads);System。out。println(pinmodepinMode);TomcattomcatnewTomcat();Contextcontexttomcat。addContext(,(newFile(。))。getAbsolutePath());Tomcat。addServlet(context,SERVLETNAME,newVirtualThreadHandleServlet());context。addServletMappingDecoded(SERVLETPATH,SERVLETNAME);ConnectorconnectornewConnector();ProtocolHandlerprotocolHandlerconnector。getProtocolHandler();if(protocolHandlerinstanceofAbstractProtocollt;?protocol){protocol。setAddress(InetAddress。getByName(127。0。0。1));protocol。setPort(9091);ThreadFactoryfactoryThread。ofVirtual()。name(embedtomcatvirtualWorker,0)。factory();Classlt;?klassClass。forName(java。util。concurrent。ThreadPerTaskExecutor);MethodHandlemethodHandleMethodHandles。privateLookupIn(klass,MethodHandles。lookup())。findStatic(klass,create,MethodType。methodType(klass,newClass〔〕{ThreadFactory。class}));ExecutorServiceexecutor(ExecutorService)methodHandle。invoke(factory);protocol。setExecutor(executor);}tomcat。getService()。addConnector(connector);tomcat。start();}} 这里VirtualThreadHandleServlet匹配所有格式的请求路径并且处理所有请求方法类型的请求。默认的虚拟线程调度器没有为虚拟线程设置名称,也就是如果使用Executors。newVirtualThreadPerTaskExecutor()作为Tomcat的线程池是最终调用看到的控制台输出的虚拟线程名称是一个空字符串。所以笔者这里用MethodHandle直接实例化了默认修饰符没有开放访问权限的ThreadPerTaskExecutor类,基于一个自定义的ThreadFactory强制构造了一个自定义ThreadPerTaskExecutor实例。调用main方法启动后见控制台输出: tomcatvirtualthread3 这里确认了Tomcat启动完成侦听127。0。0。1:9091,通过浏览器或者POSTMAN发送任意请求例如http:127。0。0。1:9091foo就能看到响应结果和控制台输出: tomcatvirtualthread4 这里的Tomcat线程池甚至可以设计为一个完全自定义的虚拟线程调度器,可以参考前面一篇文章,这里不再赘述。暂时无法在SpringBoot体系中使用 由于Servlet规范问题,Tomcat的升级导致一些接口迁移到jakarta。servlet包中,例如jakarta。servlet。Servlet,此时SpringBoot体系即使是最新版本(当前时间为20221007前后,此时最新版本为2。7。4)使用的是还是旧的规范,对应的类是javax。servlet。Servlet,这只是其中一个接口,大部分和HTTP协议或者Servlet规范相关的接口都存在这个包升级不兼容的问题,需要等待SpringBoot升级为embedtomcat10。1。0才能适配虚拟线程。小结 Demo项目仓库:Github:https:github。comzjcscutframeworkmeshtreemastertomcatvirtualthread 来源:https:mp。weixin。qq。coms5bbc1QMFEK82sBmeXVm3w