randomaccessfile多线程并发写入需为每个线程创建独立实例,因各线程fd独立维护偏移量,seek+write非原子操作易致覆盖;须统一计算分片起止位置、严格对齐写入范围,并通过外部状态记录与校验保障完整性。

RandomAccessFile 多线程并发写入同一文件时出现指针覆盖,根本原因不是“指针被抢”,而是每个线程操作的是独立的文件描述符(fd),各自维护一份文件偏移量。系统不保证跨线程 seek + write 的原子性,一旦多个线程对同一个 RandomAccessFile 实例调用 seek() 和 write(),极易发生写入位置错位、内容覆盖或部分丢失。
必须为每个线程创建独立的 RandomAccessFile 实例
共用一个 RandomAccessFile 对象是高危操作——即使加 synchronized 或 ReentrantLock 也无效,因为 JVM 层无法协调底层 POSIX 文件指针的并发行为。
- 每个线程应 new RandomAccessFile("target.bin", "rw"),打开自己专属句柄
- 构造后立即调用 raf.seek(startOffset),确保从预分配的字节区间起始位置开始写
- 写入长度严格等于该分片的字节数(如 read(byte[], 0, chunkSize) 后 write(byte[], 0, actualRead))
写入位置必须由分片逻辑统一计算并严格对齐
所有线程使用的 startOffset 和写入长度,必须基于同一套切块公式,且全程用 long 类型防溢出(尤其 >2GB 文件)。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 先获取真实文件总长:long total = raf.length()(注意不能缓存,每次切片前重读)
- 每块大小:long chunkSize = total / threadCount
- 第 i 块起始:long start = i * chunkSize
- 第 i 块结束:long end = Math.min(start + chunkSize, total)
- 该线程写入偏移必须为 start,写入长度为 (int)(end - start)
避免“伪空洞”误导,不依赖稀疏文件行为
seek() 后 write() 在 Java 层不会跳过中间字节生成真正空洞;JVM 会补零或触发预分配,导致磁盘实际占用与逻辑长度一致。不要指望靠 seek 跳过区域来“节省空间”或“预留位置”。
- 若需写入非连续段(如 HTTP Range 下载),必须确保服务端返回 206 Partial Content,且响应体长度与预期 chunkSize 匹配
- 写入前检查目标文件是否可写、是否存在;写入后建议调用 raf.getFD().sync() 确保落盘(断电防护)
- 不要在写入中途调用 getFilePointer() 判断位置——它返回的是当前指针,不是你期望的写入点
合并或续传需额外状态协调
如果任务中断后要恢复,不能仅靠文件长度判断进度,因为未完成分片可能已写入部分数据但未填满。
- 需外部记录每个 chunkIndex 的完成状态(如 Redis、本地 .meta 文件)
- 重启时比对 meta 中已完成索引与文件实际长度,跳过已成功写入的块
- 校验分片完整性(如 MD5/SHA256 或 CRC32)可防止网络传输错误导致的静默损坏










