java21升级事项

文章目录

  • javax.servlet包名变更
  • 反射安全增强
    • 编译结果必须保留参数名称
    • 不允许反射读取内部类的信息
  • 其他组件升级
    • apache httpclient升级
    • mybatis升级
    • junit升级
  • 启用虚拟线程
    • 适用于阻塞式IO调用
    • 限制
    • springMVC
    • dubbo provider

jdk8+springboot2迁移到jdk21+springboot3.2

javax.servlet包名变更

变为了jakarta.servlet,影响所有web filter的实现
https://www.oschina.net/news/106465/oracle-killed-java-ee

反射安全增强

编译结果必须保留参数名称

更新maven-compiler-plugin的参数,升级lombok版本

<plugin>
    <groupId>org.apache.maven.pluginsgroupId>
    <artifactId>maven-compiler-pluginartifactId>
    <version>3.11.0version>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>1.18.30version>
            path>
        annotationProcessorPaths>
        <source>21source>
        <target>21target>
        <encoding>UTF-8encoding>
        
        <parameters>trueparameters>
    configuration>
plugin>

springmvc6.1之前,能够利用LocalVariableTableParameterNameDiscoverer来处理反射信息缺失时的参数映射,6.1版本开始舍弃了这个功能,所以必须在编译时保留参数名称信息

不允许反射读取内部类的信息

dubbo使用javassist创建客户端代理的时候,大量访问了内部类,java16开始默认禁止反射JDK内部类的信息

增加启动参数,跳过限制

--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.math=ALL-UNNAMED

其他组件升级

除了springboot升级到3.2之外,还有一些常用组件的强制升级:

apache httpclient升级

<dependency>
    <groupId>org.apache.httpcomponents.client5groupId>
    <artifactId>httpclient5artifactId>
    <version>5.2.1version>
dependency>

mybatis升级

<dependency>
    <groupId>org.mybatisgroupId>
    <artifactId>mybatisartifactId>
    <version>3.5.13version>
dependency>
<dependency>
    <groupId>org.mybatisgroupId>
    <artifactId>mybatis-springartifactId>
    <version>3.0.3version>
dependency>

junit升级

<dependency>
    <groupId>org.junit.jupitergroupId>
    <artifactId>junit-jupiter-engineartifactId>
    <version>5.10.1version>
    <scope>testscope>
dependency>

启用虚拟线程

适用于阻塞式IO调用

stackful coroutine
之前的java执行线程全部和操作系统线程一一对应;

java虚拟线程上的任务一旦因等待信号量、等待IO响应(数据库调用、http调用、dubbo调用)而挂起,jvm runtime会立即备份stack信息并把操作系统线程资源转让给其他可执行的虚拟线程,cpu密集型任务执行一段时间后也会转让资源;这与goroutine、openresty cosocket的实现机制一致,都属于stackful coroutine。

stackful coroutine通过编程语言runtime来备份和还原必要的stack,减少对操作系统线程的需求,能够分配海量线程而不显著增加内存耗用、调度延迟。

stackless coroutine
ES6/C# async await、webflux、kotlin coroutine等回调式/响应式IO模型,实质是在编译期间隐式生成状态机/匿名类/闭包,属于stackless coroutine;和XMLHTTPRequest、java NIO.2等API先释放线程,获取到IO响应结果后再触发回调的机制一致。

stackless coroutine不保证IO等待前/挂起前的代码和执行回调的代码运行在同一个线程(除非是约束在单线程运行环境),所以只能应用于规定语法下的执行逻辑;但是闭包里引用的局部变量比stackful coroutine采集的stack要少,消耗更少的内存和切换时间。

总结
stackless/stackful coroutine在等待IO响应时,都是利用IOCP/ePOll等操作系统IO接口提供的事件机制,来实现挂起和恢复。

虚拟线程运行的阻塞式IO,能达到与回调式/响应式IO相近的并发处理效率,且不用对旧代码做任何改造,并保持当前的线程追踪与调试手段。

限制

synchronized块内,如果发生阻塞操作而引发线程等待,当前虚拟线程不会释放执行资源给其他虚拟线程;

synchronized块内如果存在阻塞式调用,synchronized需要改为ReentrantLock;使用以下启动参数来观察是否存在无法释放执行资源的虚拟线程:

// 打印无法切换执行资源的虚拟线程的栈,不能与-javaagent共存
-Djdk.tracePinnedThreads=short
// 限制负载虚拟线程的操作系统线程数量
-Djdk.virtualThreadScheduler.parallelism=1
-Djdk.virtualThreadScheduler.maxPoolSize=1

JNI调用期间,虚拟线程也不会释放资源

springMVC

@Configuration
public class TomcatConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        factory.addProtocolHandlerCustomizers((handler) -> {
            if (handler instanceof Http11NioProtocol) {
                Http11NioProtocol protocolHandler = (Http11NioProtocol) handler;
                // 接收连接后,等待request header到达的时间
                protocolHandler.setConnectionTimeout(20000);
                protocolHandler.setUseKeepAliveResponseHeader(true);
                protocolHandler.setKeepAliveTimeout(60000);
                protocolHandler.setMaxKeepAliveRequests(1024);
                // 使用虚拟线程处理http请求并为线程提供名称
                Thread.Builder builder = Thread.ofVirtual().name("HttpVirtualThread-", 1);
                protocolHandler.setExecutor(Executors.newThreadPerTaskExecutor(builder.factory()));
            }
        });
    }
}

dubbo provider

由于dubbo服务端内部使用多处synchronized块来控制并发,官方赞不建议切换为使用虚拟线程来处理请求,实际试用时未发现问题,可能因为流量吞吐不成为瓶颈,操作客户端tcp通道时不存在并发冲突
https://github.com/apache/dubbo/issues/11696
利用dubbo的SPI机制加入自定义线程池:

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
 
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.threadpool.ThreadPool;
 
public class VirtualThreadPool implements ThreadPool {
    @Override
    public Executor getExecutor(URL url) {
        String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
        Thread.Builder builder = Thread.ofVirtual().name(name + "-", 0);
        return Executors.newThreadPerTaskExecutor(builder.factory());
    }
}

你可能感兴趣的:(java,java)