RandomAccessFile多线程写入错乱的根本原因是未显式控制写入位置,必须为每个分片单独创建实例并seek到对应偏移量,配合原子meta文件实现安全断点续传。

RandomAccessFile 写入时文件内容错乱或覆盖
根本原因是没控制好写入位置,RandomAccessFile 不像 FileOutputStream 那样自动追加,它默认从当前指针位置写,而多线程下指针极易冲突。
- 每个线程必须用
seek()显式跳转到自己负责的字节偏移量,不能依赖“打开即末尾” - 不要在多个线程间共用同一个
RandomAccessFile实例——哪怕加锁也不安全,JVM 层不保证跨线程 seek/write 原子性 - 推荐为每个下载分片(如 0–1MB、1–2MB)单独 new 一个
RandomAccessFile,构造时用"rw"模式,并立即seek(startOffset) - 写完后不用
close()太频繁,但也不能一直开着——建议写满一个缓冲区(如 8KB)就flush(),全部写完再 close
断点续传时如何安全读取已下载进度
靠检查目标文件长度判断续传起点是常见但危险的做法:文件可能被其他进程截断、写入中途崩溃、或磁盘满导致实际写入字节数少于 length() 返回值。
- 必须配合外部记录机制,比如在同目录下存一个
.download.meta文件,里面用纯文本写入totalSize=10485760和completedRanges=[0-2097152,3145728-4194304] - 启动时先读 meta,再用
RandomAccessFile.length()校验——如果文件长度小于 meta 中任一 range 的 end,说明该段损坏,需重下 - 避免用 JSON 或序列化格式存 meta:Java 版本升级或对象结构变会导致解析失败;纯 key=value + 行分割最稳
多线程写同一个文件是否需要 synchronized 或 ReentrantLock
不需要锁文件或锁 RandomAccessFile,但必须锁“分片分配逻辑”和“meta 更新逻辑”。
- 写操作本身不冲突:只要每个线程严格写自己分片的 offset 区间,物理上互不重叠,底层文件系统能保证原子写(对 ≤ 4KB 的 write 调用)
- 真正要锁的是:从待下载区间队列里取下一个 range 的动作,以及把成功完成的 range 写入 meta 文件的那一刻
- 用
ConcurrentLinkedQueue管理未分配 range,比手动加锁更轻量;meta 更新建议用Files.write(Paths.get("x.meta"), lines, StandardOpenOption.WRITE)原子替换,而非追加
RandomAccessFile 在 Windows 上抛出 IOException: The process cannot access the file because another process has locked a portion of the file
这是 Windows 文件锁机制导致的典型问题:即使你没显式调用 lock(),某些杀毒软件、索引服务或 IDE 会静默持有句柄,且 RandomAccessFile 在 Windows 下对并发访问更敏感。
立即学习“Java免费学习笔记(深入)”;
- 绕过方法:改用
FileChannel+MappedByteBuffer,它底层走内存映射,绕过部分系统级锁(但注意:映射区域不能超过 2GB 单段) - 或者换用
Files.write(path, bytes, StandardOpenOption.WRITE, StandardOpenOption.SPARSE),配合SeekableByteChannel手动定位,兼容性更好 - 调试时用
handle.exe -p your_java_pid(Sysinternals 工具)查谁占着文件句柄,比看错误信息管用得多










