实现缓存的按需加载
- 在获取写锁之后,我们并没有直接去查询数据库,而是在代码⑥⑦处,重新验证了一次缓存中是否存在,再次验证如果还是不存在,我们才去查询数据库并更新本地缓存。
- 为什么需要再次验证:原因是在高并发的场景下,有可能会有多线程竞争写锁。再次验证的方式,能够避免高并发场景下重复查询数据的问题。
读写锁的升级与降级
- 锁的升级:先获取读锁,然后升级为写锁
- ReadWriteLock 并不支持这种升级
- 读锁还没有释放,此时获取写锁,会导致写锁永久等待,最终导致相关线程都被阻塞,永远也没有机会被唤醒。所以锁是不允许升级的
- 锁的降级,允许,比如写锁释放前降级为读锁,之后释放写锁,读锁仍然存在且有效
读写锁总结
- 读写锁类似于 ReentrantLock,也支持公平模式和非公平模式。
- 读锁和写锁都实现了 java.util.concurrent.locks.Lock 接口,所以除了支持 lock() 方法外,tryLock()、lockInterruptibly() 等方法也都是支持的。
- 但是有一点需要注意,那就是只有写锁支持条件变量,读锁是不支持条件变量的,读锁调用 newCondition() 会抛出 UnsupportedOperationException 异常。
- 实现缓存的案例中并没有考虑到DB数据和缓存数据的一致性
- 超时机制:加载进缓存的数据是有时效的,当缓存数据超时,也就失效了。而访问缓存中失效的数据,会触发缓存重新从源头把数据加载进缓存。
- 变化时反馈:在源头数据发生变化时,快速反馈给缓存;例如 MySQL 作为数据源,可以通过近实时地解析 binlog 来识别数据是否发生了变化,如果发生了变化就将最新的数据推送给缓存。
- 数据库和缓存的双写方案:保证数据一致性 Redis 延时双删策略。