在Java应用中,当同一份程序被启动多次或者以多个进程方式部署时,它们可能同时读写同一个磁盘文件。普通的线程同步工具只能在一个JVM内部生效,无法跨进程约束。Java的FileLock基于操作系统的文件锁能力,能够在进程级别实现互斥与并发控制。

FileLock是什么
FileLock是java.nio.channels包下的一个类,通过FileChannel的lock或tryLock方法获取。它锁定的是文件区域,而不是Java对象,因此对其他进程同样有效。根据模式不同,可分为排他锁和共享锁。
排他锁与共享锁
- 排他锁:同一时间只能有一个进程持有,适合写操作。
- 共享锁:多个进程可同时持有,适合读操作,但写进程无法获取。
基础使用示例
下面演示如何在一个进程中获取排他锁,执行写入后释放:
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
public class LockDemo {
public static void main(String[] args) throws Exception {
// 以读写模式打开文件
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();
// 尝试获取排他锁,阻塞直到可用
FileLock lock = channel.lock();
try {
// 模拟多实例下的安全写入
file.seek(file.length());
file.write("instance-writen".getBytes());
} finally {
// 释放锁并关闭
lock.release();
channel.close();
file.close();
}
}
}
多实例运行的控制策略
当系统启动多个Java实例时,可以让每个实例在启动时尝试对同一个锁文件获取排他锁。如果获取失败,说明已有实例在运行,可选择退出或进入只读模式。
单实例限制示例
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
public class SingleInstance {
public static void main(String[] args) throws Exception {
File lockFile = new File("app.lock");
RandomAccessFile raf = new RandomAccessFile(lockFile, "rw");
FileChannel ch = raf.getChannel();
FileLock lock = ch.tryLock();
if (lock == null) {
System.out.println("已有实例运行,本进程退出");
System.exit(1);
}
// 正常业务逻辑
System.out.println("实例启动成功");
// 保持运行
Thread.sleep(Long.MAX_VALUE);
}
}
注意事项
| 场景 | 说明 |
|---|---|
| 网络文件系统 | 部分NFS对文件锁支持不完善,需充分测试 |
| JVM崩溃 | 操作系统通常自动释放死进程锁 |
| 锁范围 | 可锁定部分区域,并非必须整个文件 |
使用FileLock时,应避免在锁内执行过慢逻辑,以减少其他进程的等待。结合tryLock非阻塞调用,可以灵活设计多实例的协作方式。
FileLock解决的是进程间文件资源的并发安全问题,不能替代线程内的synchronized或ReentrantLock。
合理利用Java的FileLock,可以有效控制多实例运行时的文件访问冲突,提升部署架构的健壮性。