system.arraycopy本身线程安全但无同步语义,其并发安全性取决于调用上下文:分片独占写、环形缓冲区、不可变源+私有目标是三种安全模式,共用dest数组或未同步扩容则存在数据撕裂与可见性风险。

System.arraycopy 本身是线程安全的单次操作,但“在并发下的表现”不取决于它是否加锁,而取决于你如何用它——它不保证多线程对同一目标数组的写入安全,也不协调源数据一致性。真正决定并发表现的,是调用上下文,不是 arraycopy 这一行代码。
它不加锁,也不同步,只做一次内存搬运
System.arraycopy 是 JVM 底层指令级原语,执行过程原子(单次调用不可中断),但不带任何同步语义:
- 它不会自动加 synchronized、不会检查 volatile、也不会触发 happens-before 关系
- 如果两个线程同时对同一个 dest 数组不同区域调用 arraycopy(比如线程 A 写 [0..1023],线程 B 写 [1024..2047]),只要地址不重叠,物理上可并行,但Java 内存模型不保证结果可见性——除非 dest 数组字段声明为 volatile 或写后显式 fence
- 若多个线程往同一 dest 区域写(哪怕 offset 不同但实际覆盖同一内存单元),会发生数据撕裂,结果未定义
源数组读取在并发中可能“看到旧值”
arraycopy 读 src 数组时,不做 volatile 读或内存屏障:
- 若 src 是共享数组,且其他线程刚更新了其中某些元素,当前线程调用 arraycopy 时可能读到过期缓存值(尤其在弱一致性架构如 ARM 上)
- 这不是 arraycopy 的 bug,而是 Java 内存模型的默认行为:普通数组访问无同步语义
- 解决办法不是改 arraycopy,而是确保读前有同步点——例如用 ReentrantLock 保护 src 更新与后续拷贝,或把 src 声明为 final + 初始化完成后再发布
安全并发使用的三个典型模式
不用锁也能高效协作,关键在于结构设计:
- 分片独占写:预分配一个大 buffer(如 byte[1024*1024]),按 slot 划分(每 slot 8KB),每个线程固定使用一个 slot 起始偏移;用 arraycopy 往自己 slot 写,互不干扰,无需同步
- 生产者-消费者环形缓冲区:用 CAS 控制 readIndex/writeIndex,arraycopy 只在单线程(如消费线程)内执行“从 ring buffer 搬出一段到本地 buffer”,天然串行化
- 不可变源 + 复用目标:src 数组构造后不再修改(如配置快照、协议头模板),多个线程并发调用 arraycopy 将其复制到各自私有 dest 数组——源安全,目标隔离
千万别踩的并发坑
这些写法看着简洁,实际危险:
- 让多个线程共用同一个 dest 数组,并靠 offset 参数“错开写入”——没有同步机制时,JVM 不保证写操作的发布顺序,其他线程可能看到部分更新的中间态
- 在未同步的 ArrayList.add() 中直接调 arraycopy 扩容——扩容本身线程安全,但若多个线程同时触发扩容,可能 new 出多个新数组,最终只保留一个,其余泄漏,且旧数组引用可能被提前 GC
- 用 arraycopy 实现对象数组的“浅拷贝同步”,却期望下游线程立即看到新引用——Object[] 元素是引用,但数组本身字段未 volatile,新引用对其他线程不可见
它快,是因为轻;它并发要稳,是因为你得把它放在稳的结构里。不是函数决定并发,是设计决定并发。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











