filechannel.position() + write() 组合不线程安全,因两步间存在竞态窗口;应改用原子的write(buf, pos)或独占channel。

在超大文件中间位置用 FileChannel.position(newPos) 定位后写入,**本身不保证线程安全**。关键不在“写”动作是否原子,而在于“定位 + 写”这个组合操作的竞态风险。
position(newPos) 是单线程串行化的操作
调用 position(long) 会修改通道内部的当前文件偏移量(file position)。该方法被设计为**单线程执行**:如果一个线程正在执行 position() 或 position(newPos),其他线程对同一 FileChannel 实例调用任何影响位置或大小的方法(如 read()、write()、truncate())都会被阻塞,直到前一个操作完成。
- 这意味着多个线程连续调用
position(1000000).write(buf)不会“交错定位”,但也不等于写入内容不会错乱 - 因为
position()和write()是两个独立方法,中间存在时间窗口——另一线程可能插入并改变 position
并发写入中间位置的真实风险点
即使你每次写前都显式设置 position(newPos),以下情况仍会导致数据错乱:
- 线程 A 调用
position(100_000_000)→ 成功设置位置 - 线程 B 紧接着调用
position(200_000_000)→ 覆盖了 A 刚设的位置 - 线程 A 执行
write(buf)→ 实际写入的是 200_000_000 处,而非预期位置 - 或者更隐蔽:A 设置位置后,B 执行了一次
write()(无显式 position),它会从 B 自己上次留下的位置写,同时把全局 position 推进,间接干扰 A 的后续 write
安全做法:避免共享 channel 实例或加锁控制
对同一文件做确定位置的并发写,推荐两种明确可控的方式:
-
用带 position 参数的绝对写:直接调用
write(buf, newPos)。这个方法是原子的——它不改变通道的全局 position,而是临时定位并写入,线程间互不影响 -
每个线程独占 FileChannel 实例:通过
RandomAccessFile.getChannel()为每个写任务打开独立 channel,各自维护自己的 position,天然隔离 - 若必须复用 channel,需在外层加同步(如
synchronized(channel)或ReentrantLock),确保position() + write()组合不被中断
补充:文件空洞与 position 超出文件尾
将 position 设到超过当前文件长度(例如文件长 1GB,设 position=2GB)是合法的。后续 write() 会扩展文件,并在中间填充零字节(即产生“空洞”)。这种行为在线程安全层面没有额外风险,但要注意:多个线程同时向不同高位 position 写,可能导致文件稀疏且实际磁盘占用不可预测。










