java21升级事项


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.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.11.0</version>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.30</version>
            </path>
        </annotationProcessorPaths>
        <source>21</source>
        <target>21</target>
        <encoding>UTF-8</encoding>
        <!--保留参数原名,否则springmvc参数注解不配置名称时无法正常运行-->
        <parameters>true</parameters>
    </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.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.2.1</version>
</dependency>

mybatis升级

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>3.0.3</version>
</dependency>

junit升级

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.10.1</version>
    <scope>test</scope>
</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等回调式/响应式编程模型,实质是在编译期间隐式生成状态机/匿名类/闭包,属于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());
    }
}
  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值