原子升级依赖内存操作不可分割性而非流程控制语句,核心是system.arraycopy映射为硬件级不可中断指令,配合双数组、结构体数组或位图分离布局,预分配后单次批量更新,并通过volatile数组、varhandle或atomiclongarray保障跨线程可见性。

不能靠“流程控制语句”实现原子升级,真正起作用的是内存操作的不可分割性与结构设计。所谓“原子块内一次性完成”,本质是把多处版本号变更封装为单次、不可中断、无中间态暴露的底层内存搬运动作,而非用 if/for/try-catch 等 Java 流程语法来“模拟”原子性。
核心不是流程,而是搬运动作本身不可分
System.arraycopy() 调用在 JVM 层被映射为一条底层指令(如 rep movsq 或 SIMD 批量移动),执行过程中不会被线程调度打断,也不会对外暴露部分写入状态。只要目标数组是连续定长结构,一次 arraycopy 就能确保所有指定位置的版本号同步更新——这是硬件级保障,不是代码逻辑能“控制”出来的。
键值对结构必须支持整体平移
若键值对是散列对象(如 HashMap.Entry[]),每个元素含引用+int+long,内存不连续,arraycopy 无法安全批量刷新 version 字段。必须采用以下任一布局:
- 双数组并行:分开维护 long[] versions 和 Object[] values,升级时只调用 arraycopy 刷新 versions 片段
- 结构体数组(long[] 模拟):每 3 个 long 单元存 [key_hash, version, timestamp],通过固定偏移计算 srcPos,用一次 arraycopy 更新整段 version 字段
- 位图分离式:主数据 byte[] 不可变,版本信息单独存在紧凑 int[] 中,升级仅更新该数组,读取时组合即可
升级过程必须规避分步写入
禁止逐个下标赋值(versions[i] = newVer),否则读线程可能看到新旧混杂状态。正确做法是:
- 预分配临时数组 long[] newVersions,填入全部目标新版本值
- 定位主版本数组中对应区间:srcPos(起始索引)、length(条目数)
- 执行唯一一次调用:System.arraycopy(newVersions, 0, versions, srcPos, length)
该调用要么全部成功,要么抛异常中断,绝无“前5个更新了、后3个没更新”的中间态。
跨线程可见性需额外保障
arraycopy 不保证其他线程立即看到结果。若读线程直接访问 versions[i],必须配合:
- volatile long[](JDK 9+ 支持数组元素 volatile 语义)
- VarHandle 以 release/store 模式写入(推荐,更精确控制内存屏障)
- 或使用 AtomicLongArray 替代普通 long[],并在 arraycopy 后对每个位置调用 lazySet(轻量级写发布)











