Apache Ignite中StripedCompositeReadWriteLock数组越界异常的原因与解决
问题场景
- 基于Kotlin开发的Ignite客户端,依赖
ignite-core和ignite-spring - 运行数天后触发
ArrayIndexOutOfBoundsException,异常中索引为负数且每次数值不同,栈顶指向org.apache.ignite.internal.util.StripedCompositeReadWriteLock.getReadHoldCount(StripedCompositeReadWriteLock.java:120) - 重启服务后异常消失,但运行数天会再次出现
- 环境:Ignite 2.11.0,Java 18
可能原因
1. IDX_GEN整数溢出(核心原因)
StripedCompositeReadWriteLock中的IDX_GEN是AtomicInteger类型,用于生成线程对应的锁条带索引,计算逻辑为IDX_GEN.getAndIncrement() & mask(mask默认是15,对应长度16的锁数组)。当IDX_GEN递增到Integer.MAX_VALUE后,继续递增会溢出为负数,此时与mask按位与仍可能得到负索引,直接触发数组越界。
2. 线程复用与ThreadLocal异常
若客户端频繁创建销毁线程,且ThreadLocal未及时清理,可能导致锁索引的线程本地存储逻辑异常,进而生成负索引。但这种情况的触发概率远低于整数溢出。
3. Java版本兼容性隐患
Ignite 2.11.0发布时Java 18尚未推出,部分JDK内部API的行为变化可能间接影响锁索引计算,但结合栈信息来看,该因素的可能性极低。
无需重启的规避方案
1. 升级Ignite到修复版本
Ignite 2.12.0及后续版本已将IDX_GEN从AtomicInteger替换为AtomicLong,彻底解决了整数溢出问题,这是最根本的解决方案。
2. 自定义锁实现(临时规避)
若暂时无法升级,可通过反射或自定义子类替换StripedCompositeReadWriteLock中的IDX_GEN为AtomicLong,避免整数溢出。
3. 调整锁条带数量(权宜之计)
通过系统属性IGNITE_STRIPED_LOCKS_CNT增大锁条带数量,虽然不能彻底解决溢出,但能延长异常出现的周期。
4. 优化线程配置
使用固定大小线程池复用线程,减少线程频繁创建销毁的情况,降低ThreadLocal异常清理导致索引计算错误的概率。
验证方式
通过反射获取StripedCompositeReadWriteLock的IDX_GEN字段,定期打印其值,当数值接近Integer.MAX_VALUE时观察是否触发异常,即可确认是否为溢出导致。
内容的提问来源于stack exchange,提问作者Mikhail Baksheev

