在Tomcat中启用虚拟线程特性

前提趁着国庆前后阅读了虚拟线程相关的源码,写了一篇《虚拟线程 - VirtualThread源码透视》,里面介绍了虚拟线程的实现原理和使用示例 。需要准备做一下前期准备:

  • 安装OpenJDK-19或者Oracle JDK-19
  • 准备好嵌入式Tomcat的依赖,需要引入三个依赖包,分别是tomcat-embed-core、tomcat-embed-el和tomcat-embed-websocket,版本选用10.1.0+
查看Tomcat官方文档的CHANGELOG:
在Tomcat中启用虚拟线程特性

文章插图
支持Loom项目的Tomcat最低版本为10.1.0-M16,对应的正式版是10.1.0(当前时间为2022-10-07前后),低于此版本因为大量API还没有适配虚拟线程,主要是没有改造监视器锁的引用导致虚拟线程pin到载体(平台)线程等问题,因此别无他选 。另外,重要的提醒说三次:
  • 本文是实验性质 , 在未完全证实改造功能可以应用生产环境前需要谨慎评估,或者先别使用于生产环境
  • 本文是实验性质 , 在未完全证实改造功能可以应用生产环境前需要谨慎评估 , 或者先别使用于生产环境
  • 本文是实验性质,在未完全证实改造功能可以应用生产环境前需要谨慎评估,或者先别使用于生产环境
引入依赖引入以下依赖:
<dependency>    <groupId>org.apache.tomcat.embed</groupId>    <artifactId>tomcat-embed-core</artifactId>    <version>10.1.0</version></dependency><dependency>    <groupId>org.apache.tomcat.embed</groupId>    <artifactId>tomcat-embed-el</artifactId>    <version>10.1.0</version></dependency><dependency>    <groupId>org.apache.tomcat.embed</groupId>    <artifactId>tomcat-embed-websocket</artifactId>    <version>10.1.0</version></dependency>编程式初始化Tomcat为了使用反射调用一些java.base模块下没开放的依赖包和跟踪虚拟线程栈 , 程序运行时候加入下面的VM参数:
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED -Djdk.tracePinnedThreads=full在IDEA的运行配置中是这个样子:
在Tomcat中启用虚拟线程特性

文章插图
接着编写一个HttpServlet实现:
public class VirtualThreadHandleServlet extends HttpServlet {    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");    @Override    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        Thread thread = Thread.currentThread();        System.out.printf("service by thread ==> %s, is virtual ==> %s, carrier thread ==> %s\n",                thread.getName(), thread.isVirtual(), getCurrentCarrierThreadName(thread));        resp.setStatus(HttpServletResponse.SC_OK);        resp.setHeader("Content-Type", "application/json");        String content = "{\"time\":" + "\"" + LocalDateTime.now().format(FORMATTER) + "\"}";        resp.getWriter().write(content);    }    private static String getCurrentCarrierThreadName(Thread currentThread) {        if (currentThread.isVirtual()) {            try {                MethodHandle methodHandle = MethodHandles.privateLookupIn(Thread.class, MethodHandles.lookup())                        .findStatic(Thread.class, "currentCarrierThread", MethodType.methodType(Thread.class));                Thread carrierThread = (Thread) methodHandle.invoke();                return carrierThread.getName();            } catch (Throwable e) {                e.printStackTrace();            }        }        return "UNKNOWN";    }}

推荐阅读