Skip to content

文件锁

你有没有想过这个问题:

两个 JVM 同时写同一个文件,会发生什么?

答案是:文件内容会交错,甚至损坏。

两个进程同时写入同一个文件,没有任何协调机制,结果是不可预测的。

文件锁:多进程访问同一个文件

Java 通过 FileLock 实现文件锁,确保多进程访问同一文件时的数据一致性。

基本用法

获取锁

java
RandomAccessFile file = new RandomAccessFile("shared.txt", "rw");
FileChannel channel = file.getChannel();

// 获取排他锁(写锁)
FileLock lock = channel.lock(); // 阻塞直到获取锁
try {
    // 临界区操作
    file.writeBytes("data");
} finally {
    lock.release(); // 释放锁
    file.close();
}

// 非阻塞获取锁
FileLock tryLock = channel.tryLock();
if (tryLock != null) {
    // 获取成功
    try {
        // 操作
    } finally {
        tryLock.release();
    }
} else {
    // 获取失败(已被其他进程持有)
}

锁类型

java
// 排他锁(独占锁)
FileLock exclusiveLock = channel.lock();

// 共享锁(读锁)
FileLock sharedLock = channel.lock(0L, Long.MAX_VALUE, true); // true = 共享

锁的范围

java
// 锁定文件的全部
FileLock fullLock = channel.lock();

// 锁定文件的指定范围
FileLock partialLock = channel.lock(100, 200, false); // 锁定 100~200 字节

锁的特性

跨 JVM 有效

java
// JVM A
FileLock lock = channel.lock();

// JVM B 尝试获取锁,会阻塞(排他锁)或失败(共享锁)

// OS 层面的文件锁,对所有进程有效

操作系统差异

java
// Windows:共享锁不真正共享,写锁独占
// Unix/Linux:支持真正的共享锁

自动释放

java
// 关闭 Channel 时锁自动释放
channel.close(); // 锁也被释放

// 建议显式释放
lock.release();

实战:多进程日志文件

java
public class ProcessSafeLogger {
    private final FileChannel channel;
    private final FileLock lock;

    public ProcessSafeLogger(String path) throws IOException {
        RandomAccessFile file = new RandomAccessFile(path, "rw");
        channel = file.getChannel();
        lock = channel.lock();
    }

    public void log(String message) throws IOException {
        // 确保在锁内写入
        byte[] data = (message + "\n").getBytes(StandardCharsets.UTF_8);
        channel.write(ByteBuffer.wrap(data), channel.size());
    }

    public void close() throws IOException {
        lock.release();
        channel.close();
    }
}

NIO Files 的文件锁

java
// NIO Files 本身不直接支持文件锁
// 需要通过 FileChannel 获取锁

Path path = Path.of("shared.txt");
try (FileChannel ch = FileChannel.open(path,
        StandardOpenOption.READ,
        StandardOpenOption.WRITE)) {
    FileLock lock = ch.lock();
    try {
        // 临界区操作
    } finally {
        lock.release();
    }
}

注意事项

锁的粒度

java
// ❌ 文件锁是粗粒度的
// 锁定整个文件,其他进程无法访问任何部分
FileLock lock = channel.lock();

// ✅ 细粒度:只锁定需要同步的部分
FileLock lock = channel.lock(position, size, shared);
// 其他进程可以访问锁范围之外的部分

死锁

java
// ❌ 多个进程按不同顺序获取多个文件的锁 → 死锁
// JVM A: 锁 A → 锁 B
// JVM B: 锁 B → 锁 A

// 解决:所有进程按相同顺序获取锁

总结

┌─────────────────────────────────────────────────────────────────┐
│  文件锁:多进程访问同一文件的协调机制                             │
│                                                                 │
│  排他锁:阻止其他读写                                             │
│  共享锁:允许并发读                                               │
│  锁在关闭 Channel 或调用 release() 时释放                         │
│  文件锁是 OS 级别的,跨 JVM 有效                                  │
│                                                                 │
│  口诀:多进程写同一文件,FileLock 来帮忙                          │
└─────────────────────────────────────────────────────────────────┘

基于 VitePress 构建