arrays.fill() 不是线程安全的,因其无锁、无可见性保证,多线程并发填充同一数组会导致数据覆盖和脏读;安全方案包括分段并行填充、线程本地数组合并、使用并发容器;二维数组可按行安全并发填充。

Arrays.fill() 本身不是线程安全的操作——它只是对数组内存做同步写入,不加锁、不保证可见性。在多线程环境下直接并发调用 Arrays.fill() 填充**同一个数组**,会导致数据覆盖、部分写入丢失,甚至产生不可预测的中间状态。
为什么 Arrays.fill() 在多线程下不安全?
它本质是单线程循环写入(或 JVM 优化后的内存块拷贝),没有原子性保障:
- 多个线程同时对同一数组调用 fill,可能交错写入不同位置,但无法控制“哪段先写完”
- 即使每行填充范围不重叠(如线程 A 填 [0,500),线程 B 填 [500,1000)),JVM 不保证写入顺序和内存可见性,其他线程读取时可能看到部分更新的脏值
- 没有 volatile 语义,也没有 synchronized 或 CAS 机制,无法确保填充结果对其他线程立即可见
安全填充的三种可行方案
根据场景选择合适方式,避免用“加锁+fill”这种低效组合:
-
分段并行填充(推荐):将大数组按索引区间拆分,每个线程负责一段,且各段互不重叠
例如:Arrays.fill(arr, start, end, val)配合 ForkJoinPool 或 ExecutorService 分配任务;需确保start和end严格划分、无重叠、无遗漏 - 线程本地数组 + 合并:每个线程创建自己的数组并 fill,最后由主线程合并(适用于结果可聚合的场景,如统计缓冲区)
-
使用并发安全容器替代:若目标是共享状态初始化,考虑
AtomicIntegerArray或ConcurrentHashMap等原生并发结构,而非在普通数组上强行并发 fill
二维数组的并发填充要注意什么?
Java 二维数组是“数组的数组”,int[][] matrix 的每一行 matrix[i] 是独立对象。因此:
- 多个线程分别对不同行调用
Arrays.fill(matrix[i], val)是安全的(只要不操作同一行) - 可用
IntStream.range(0, matrix.length).parallel().forEach(i -> Arrays.fill(matrix[i], -1))实现高效并行——这是最常用也最稳妥的二维并发初始化方式 - 切忌用
Arrays.fill(matrix, row)(填引用)再并发修改row,这会引发所有行联动修改
什么时候不该用并发 fill?
并非所有场景都适合并行化:
- 数组长度小于 1 万:线程调度开销可能超过收益,串行
Arrays.fill()更快更稳 - 需要与其他操作构成原子事务(如“填充+标记完成”):应封装为 synchronized 方法或使用
CountDownLatch协调 - 填充后立即被其他线程读取:必须配合
volatile数组引用(如volatile int[] data)或显式Thread.onSpinWait()/ 内存屏障,否则读线程可能看不到最新值











