3. 文件锁机制:共享锁、排他锁、乐观锁、悲观锁

说到文件锁,很多同学第一反应就是「加锁嘛,防止冲突」。但实际用起来,坑比想象的多。我这些年做高并发文件读写,锁选错了,性能直接腰斩。选对了,吞吐量翻倍也不是梦。

今天咱们就把这四种锁掰开揉碎了讲。共享锁、排他锁、乐观锁、悲观锁,它们各自适合什么场景?怎么搭配使用?我踩过的坑,你最好别踩。

3.1 共享锁与排他锁:最基础的读写分离

共享锁(Shared Lock)和排他锁(Exclusive Lock),说白了就是「读读不互斥,读写互斥,写写互斥」。这是操作系统层面就支持的基础机制。

核心规则:

  • 多个线程可以同时持有共享锁——大家一起读,没问题
  • 排他锁只能被一个线程持有——写的时候,谁也别碰
  • 共享锁和排他锁不能同时存在——读的时候不能写,写的时候不能读

我记得有一次优化日志系统,多个进程同时写同一个文件。一开始用的排他锁,结果写入吞吐量上不去。后来改成共享锁读、排他锁写,读性能直接提升了3倍。嗯,这里要注意:共享锁只适用于读操作,如果你在共享锁下写了数据,那后果自负。

代码示例:Java中的文件锁

// 共享锁示例
FileChannel channel = new RandomAccessFile("data.log", "rw").getChannel();
FileLock sharedLock = channel.lock(0, Long.MAX_VALUE, true);  // true表示共享锁
// 读取文件内容...
sharedLock.release();

// 排他锁示例
FileLock exclusiveLock = channel.lock(0, Long.MAX_VALUE, false); // false表示排他锁
// 写入文件内容...
exclusiveLock.release();

我的经验:共享锁不要滥用。如果你只是读几行数据,加共享锁的开销可能比读数据本身还大。我一般只在「读多写少」且「读操作耗时较长」的场景下才用共享锁。

3.2 乐观锁与悲观锁:两种哲学

乐观锁和悲观锁,其实是两种完全不同的并发控制思路。悲观锁觉得「肯定会有人跟我抢」,所以先锁住再说。乐观锁觉得「应该没人跟我抢」,先干活,提交时再检查冲突。

你想想看,文件系统里用悲观锁很自然——open一个文件,加锁,读写,解锁。但乐观锁呢?它更适合「冲突概率低」的场景。

乐观锁的实现方式

文件系统里没有原生的乐观锁,但我们可以自己实现。常见做法是「版本号机制」或「时间戳机制」。

// 乐观锁伪代码
int version = readVersionFromFile();  // 读取版本号
String data = readFileContent();      // 读取数据
String newData = modify(data);        // 修改数据

// 写入时检查版本号是否变化
if (compareAndSwapVersion(version, version + 1)) {
    writeFileContent(newData);        // 版本号没变,写入成功
} else {
    // 版本号变了,说明别人改过了,重试或报错
    retry();
}

我曾经踩过的坑:有一次用乐观锁做配置文件热更新,结果在高并发下频繁冲突,重试次数太多,CPU直接飙到90%。后来发现是「写操作太频繁」,乐观锁变成了「频繁重试锁」。这种情况下,还是老老实实用悲观锁吧。

3.3 四种锁的对比与选型

锁类型 适用场景 性能特点 典型问题
共享锁 多线程并发读 读性能高,写性能低 写操作被阻塞
排他锁 单线程写 写安全,读串行 读性能差
乐观锁 冲突概率低 无锁开销,重试成本 冲突时性能骤降
悲观锁 冲突概率高 锁开销固定 可能死锁

我个人习惯是:读多写少用共享锁+排他锁组合,写多读少用悲观锁,冲突概率极低用乐观锁。没有银弹,只有权衡。

3.4 实战:文件锁的避坑指南

光说理论不够,我分享几个真实踩过的坑。

  • 锁的粒度问题:别锁整个文件,除非你真的需要。我一般只锁文件的一部分,比如只锁前100字节的元数据区。粒度越小,并发越高。
  • 锁超时处理:文件锁不是无限期的。Java的FileLock支持tryLock,设置超时时间。我建议超时时间不要超过5秒,否则系统可能僵死。
  • 跨进程锁:文件锁天然支持跨进程,但要注意NFS等网络文件系统。我在NFS上吃过亏,锁有时不生效。后来改用专门的分布式锁组件。
  • 死锁预防:多个文件加锁时,一定要按固定顺序。比如先锁A再锁B,所有线程都遵守这个顺序,死锁概率就低很多。

一个小技巧:如果你用Java的FileLock,记得在finally块里释放锁。我曾经因为异常没释放锁,导致文件被锁了整整一个周末。嗯,那个周末我过得不太好。

3.5 知识体系图

下面这张图是我自己整理的,把四种锁的关系和适用场景串起来了。你一看就明白。

文件锁机制知识体系 文件锁 共享锁(读锁) 排他锁(写锁) 乐观锁 悲观锁 特性对比: • 共享锁:多读不互斥,读写互斥 → 适合读多写少 • 排他锁:完全互斥 → 适合写操作 • 乐观锁:无锁,提交时检查冲突 → 适合冲突概率低 • 悲观锁:先锁后操作 → 适合冲突概率高

这张图把四种锁的关系画得很清楚。共享锁和排他锁是操作系统层面的,乐观锁和悲观锁是应用层面的。实际项目中,我经常把共享锁+排他锁作为底层机制,在上面再叠加乐观锁或悲观锁的策略。

好了,文件锁这块就讲这么多。记住一句话:没有最好的锁,只有最合适的锁。选锁之前,先想清楚你的并发模型是什么样的。

可可精品资料库