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 知识体系图
下面这张图是我自己整理的,把四种锁的关系和适用场景串起来了。你一看就明白。
这张图把四种锁的关系画得很清楚。共享锁和排他锁是操作系统层面的,乐观锁和悲观锁是应用层面的。实际项目中,我经常把共享锁+排他锁作为底层机制,在上面再叠加乐观锁或悲观锁的策略。
好了,文件锁这块就讲这么多。记住一句话:没有最好的锁,只有最合适的锁。选锁之前,先想清楚你的并发模型是什么样的。