为什么在利用逻辑过期解决缓存击穿问题采用二重检查?
线程 n 在 m 刚释放锁的时刻到达,检测到缓存过期,尝试获取锁并成功拿到锁。但 n 在拿到锁后,没有意识到缓存已经被 m 重建,因为在获取锁之前,n 看到的缓存依然是过期的。
这里我们可以再补充和澄清几个细节,帮助你更全面地理解这个过程:
1. n 拿到锁的时刻
- 当
m线程持有锁并重建缓存时,其他线程(包括n)会检测缓存的状态。 - 当
m完成缓存重建并释放锁时,缓存已经被更新。 - 但
n可能在缓存重建和锁释放的极短时间内(或者在重建缓存之前),检测到缓存依然过期,因此它会去竞争锁。
2. 为什么 n 没有察觉到缓存重建?
在并发编程中,锁与缓存是分开管理的:
锁的作用是防止多个线程同时进行重建操作。
缓存的更新操作是独立的,并且存在短暂的时间差:
m线程完成缓存重建后释放锁,但n可能在缓存的最新状态写入 Redis 之前就获取了锁。这就是为什么n仍然认为缓存是过期的。简单来说,
n看到的缓存状态是在它获取锁之前检测的,这个状态是缓存已经过期。
3. 为什么要二重检查?
这就是为什么代码在 n 拿到锁后,会再次检查缓存的状态(二重检查):
1 | // 4.1 获取互斥锁 |
二重检查的作用:因为在 m 完成缓存重建后,n 拿到锁时缓存可能已经被更新,因此 n 需要重新检查缓存,避免不必要的重复重建。
4. 时间窗口问题
线程 n 在锁释放和缓存被更新之间有一个极小的时间窗口。在这种高并发场景下,时间差是不可避免的:
- 线程
m可能重建完缓存的一瞬间释放了锁,此时n进入了锁竞争。 - 但
n进入锁竞争时,它的视角仍然认为缓存是过期的,所以会尝试重建缓存。 - 二重检查避免了重复重建:如果缓存已经被重建,
n在获取锁后检查会发现缓存已经更新,因此不会再次重建。
5. 锁竞争和缓存重建的独立性
在高并发环境下,缓存重建和锁竞争是两个独立的流程:
- 锁竞争:控制多个线程避免同时进行耗时的数据库查询和缓存重建。
- 缓存重建:锁释放后,其他线程依然有可能拿到锁,而缓存状态可能已经发生变化。这就是为什么在
n成功获取锁后需要再次检查缓存。
总结:
- 线程
n在m刚释放锁时获取了锁,并在最初的检测中看到缓存过期,因为它的视角是在锁释放之前建立的。 - 这就是为什么
n没有直接意识到缓存已经被m重建。 - 代码中有二重检查机制,确保在
n拿到锁之后,再次确认缓存状态,避免多余的重建操作。
这种现象是并发操作中的正常现象,使用二重检查就是为了解决这个问题。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 ChenPi's Blog!
评论
TwikooGiscus






