Micronaut 并发启动卡在 ConversionService 类初始化,使用已修正的创建入口
Micronaut 并发首次初始化转换服务可能形成类初始化等待环。Core 5.2.15 修正工厂与框架创建次序,第三方直接构造仍须检查,并在新 JVM 中验证。
Micronaut 并发启动偶尔卡死,线程 dump 同时指向 ConversionService 与 DefaultMutableConversionService 的类初始化监视器,应检查是否从两个不同入口触发了首次初始化。等待业务 bean 的锁和等待 JVM 的类初始化锁,需要分开处理。
一个有辨识度的等待环是:线程 A 先开始初始化 DefaultMutableConversionService,随后等待 ConversionService;线程 B 在 ConversionService.<clinit> 创建共享转换器,又等待前者。双方各占一个类的初始化状态,增加启动线程数或延长超时不能解开这个环。
Core 5.2.2 的具体并发案例包含两个入口:一个线程调用 MutableConversionService.create(),另一个线程读取 ConversionService.SHARED。接口有 default 方法,JVM 初始化实现类时也要处理这类接口;同时接口的 SHARED 又直接构造实现类,次序便形成相互依赖。单线程常规启动不一定卡住,已经初始化过这两个类的 JVM 也可能掩盖问题。
Core 5.2.15 已修正框架自己的创建次序:MutableConversionService.create() 先读取 ConversionService.SHARED,再构造 DefaultMutableConversionService;DefaultBeanContext 也改为走这个工厂。应用需要一个可变转换服务时,优先使用这个入口:
import io.micronaut.core.convert.MutableConversionService;
MutableConversionService conversionService = MutableConversionService.create();
该写法的前提是运行中的 Core 已包含修补。旧版本的同名工厂本身也是触发入口之一,不能只改调用形式而不更新依赖。由现有平台或 Core BOM 将 Core 与 inject 相关组件统一到包含修补的版本,随后在新的 JVM 中验证。
如果应用或扩展自己调用 new DefaultMutableConversionService(),应逐处检查首次调用时机,能使用工厂的地方改用工厂。正式补丁修正的是框架内部入口,不等于所有第三方直接构造都已安全。在构造器里强行断言 SHARED 非空也不合适:SHARED 自身正在建立时,重入读取可能还是 null,修补只要求先触达它来确定初始化顺序。
卡死现场可以先保存线程 dump:
jcmd <PID> Thread.print
寻找类名和 class initialization monitor 的双向等待,而不是只看线程状态为 RUNNABLE 或 WAITING。升级后的验证要用新进程,或每轮新建隔离 ClassLoader,让两个线程同时分别调用工厂与读取 SHARED,并给 join 设置超时。所有线程按时完成才是这项场景的验收;连续运行同一个已初始化的类加载器不能证明首次初始化问题消失。
另一个相关表现是 SHARED 构造期间读到实现类尚未设置的静态字段,转换器注册器可能因此报初始化异常。这并不说明所有转换错误都来自此处;类型转换规则缺失、注册器业务异常仍应按各自堆栈排查。


