如何理解读写锁的互斥关系_共享锁S锁与排他锁X锁的兼容性

1次阅读

读锁和写锁不能同时存在。读锁允许多线程共享,写锁独占排他,二者严格互斥;MySQL 中 LOCK IN SHARE MODE 加 S 锁,FOR UPDATE 加 X 锁;Java 中 ReentrantReadWriteLock 写锁可降级不可升级,避免死锁。

如何理解读写锁的互斥关系_共享锁 S 锁与排他锁 X 锁的兼容性

读锁和写锁到底能不能同时存在?

不能。只要有一个线程持有了写锁(X 锁),其他所有线程——无论是想加读锁还是写锁——都会被阻塞;反过来,只要有一个或多个线程持有读锁(S 锁),任何线程尝试加写锁也会被阻塞。这是读写锁互斥关系的底层铁律。

常见错误现象:Thread A 拿着读锁还没释放,Thread B 却在等写锁,结果卡死;或者误以为“多个读锁之间不互斥,那读 + 写应该也能并行”,实际完全不行。

  • 读锁 → 允许多个线程同时持有(共享)
  • 写锁 → 只能被一个线程独占(排他)
  • 读锁 + 写锁 → 严格互斥,谁先到谁占坑,后到的必须等
  • 写锁 + 写锁 → 同样互斥,不可能共存

MySQL 里 SELECT …… LOCK IN SHARE MODESELECT …… FOR UPDATE 怎么选?

前者加的是共享锁(S 锁),后者加的是排他锁(X 锁)。选错直接导致并发行为失控或死锁。

使用场景:

  • 只查不改、且要防止别人在此期间改数据 → 用 LOCK IN SHARE MODE(比如查余额后准备扣款,中间不能被改)
  • 要更新 / 删除当前行 → 必须用 FOR UPDATE,否则可能读到旧值,或触发幻读 / 脏写
  • 如果只是普通查询(无后续修改意图),根本不用加锁,避免无谓阻塞

性能影响:S 锁比 X 锁“轻”,但大量并发读锁仍会拖慢写操作;而 X 锁一上,整行(甚至整个索引范围)就变成单线程瓶颈。

Java 里 ReentrantReadWriteLock 的写锁为啥能“降级”但不能“升级”?

因为升级(即已有读锁再去抢写锁)必然导致死锁:假设 Thread AThread B 都持读锁,又都试图升级为写锁,就会互相等待对方释放读锁——谁都放不了,全卡住。

实操建议:

  • writeLock().lock() 前,确保没持有该锁对应的 readLock()
  • 真需要“读后再写”,要么提前申请写锁(哪怕暂时只读),要么释放读锁后重新申请写锁(注意中间窗口期数据可能已变)
  • 降级是安全的:writeLock 释放前可直接获取 readLock,因为写锁本就排斥其他一切锁,此时加读锁不会引发竞争

为什么有时候加了读锁反而更慢?

不是锁本身慢,而是你把它用在了不该用的地方。读锁的价值只在「读多写少 + 读操作需强一致性」的场景。一旦写操作频繁,或读操作本身极轻(比如查一个配置字段),读锁就成了累赘。

容易踩的坑:

  • 在事务里对无关表加 LOCK IN SHARE MODE,结果阻塞了其他业务的写入
  • ReentrantReadWriteLock.readLock() 包裹耗时 IO 操作(如 HTTP 调用),导致写线程长时间饥饿
  • 误把数据库行级 S 锁当成应用层缓存锁,结果缓存没更新,锁还占着资源

真正关键的不是“有没有锁”,而是“锁的粒度和生命周期是否匹配业务语义”。一个 S 锁如果横跨网络调用或用户交互,它早就不是保护数据一致性,而是在制造瓶颈了。

text=ZqhQzanResources