知用网
白蓝主题五 · 清爽阅读
首页  > 电脑技巧

缓存失效策略中的双检锁模式实战解析

在高并发的Web应用中,缓存是提升性能的关键手段。但当缓存过期时,多个线程同时发现缓存为空,就会一窝蜂地冲向数据库,造成“缓存击穿”,轻则拖慢系统,重则直接压垮服务。

缓存击穿的日常场景

想象一下,某电商平台的爆款商品详情页依赖缓存展示价格和库存。凌晨一点缓存刚好过期,紧接着秒杀开始,上千用户同时刷新页面。每个请求发现缓存没数据,全跑去查数据库。数据库瞬间被挤爆,页面打不开,老板急得跳脚——这就是典型的缓存击穿。

能解决问题,但别太粗暴

最简单的办法是给缓存读取加个同步锁(synchronized),确保只有一个线程能去查数据库,其他线程等着。但这会导致所有请求排队,哪怕缓存已经重建好了,后面的请求还得等前面的锁释放,效率低下。

双检锁模式:既安全又高效

双检锁(Double-Checked Locking)模式就是为这种场景量身定制的。它的核心思想是:先检查缓存是否已存在,没有再加锁,加锁后再次确认缓存是否已被其他线程重建,避免重复加载。

下面是一个Java风格的伪代码示例,展示如何用双检锁处理缓存失效:

public class CacheService {
    private volatile Map<String, Object> cache = new HashMap<>();
    private final Object lock = new Object();

    public Object getData(String key) {
        // 第一次检查:缓存是否存在
        Object result = cache.get(key);
        if (result == null) {
            synchronized (lock) {
                // 第二次检查:防止多个线程重复重建
                result = cache.get(key);
                if (result == null) {
                    result = loadFromDB(key);  // 模拟从数据库加载
                    cache.put(key, result);
                }
            }
        }
        return result;
    }

    private Object loadFromDB(String key) {
        // 模拟耗时的数据库查询
        return "data_from_db_" + key;
    }
}

volatile 关键字不能少

注意上面代码中 cache 被声明为 volatile。这是为了防止指令重排序导致的问题。如果对象创建过程被优化重排,可能其他线程会拿到一个还未完全初始化的实例。volatile 能保证可见性和有序性,是双检锁正确运行的关键。

适用场景与注意事项

双检锁适合读多写少、重建成本高的缓存场景,比如配置信息、热点商品数据等。但如果业务逻辑本身很轻量,或者并发不高,直接加锁反而更简单可靠。另外,在分布式系统中,单机的双检锁不适用,需要配合分布式锁(如Redis实现)来处理。

把双检锁用对地方,既能防住缓存击穿,又不会拖慢正常请求,是提升系统稳定性的实用技巧之一。