变为了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之外,还有一些常用组件的强制升级:
<dependency>
<groupId>org.apache.httpcomponents.client5groupId>
<artifactId>httpclient5artifactId>
<version>5.2.1version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.13version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>3.0.3version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-engineartifactId>
<version>5.10.1version>
<scope>testscope>
dependency>
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调用期间,虚拟线程也不会释放资源
@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服务端内部使用多处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());
}
}