从Guava缓存源码提炼业务开发心法:Get方法暗藏的12个高并发设计哲学

1、前言

Guava:由Google团队开源的Java核心增强库,涵盖集合、并发原语、缓存、IO、反射等多种工具。其性能和稳定性有保障,广泛应用于各类项目中。

2、Get方法源码

java 复制代码
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
    checkNotNull(key);
    checkNotNull(loader);
    try {
        // count 表示在这个segment中存活的项目个数
        if (count != 0) {
            // 获取segment中的元素, 包含正在load的数据
            ReferenceEntry<K, V> e = getEntry(key, hash);
            if (e != null) {
                // 获取当前时间,纳秒
                long now = map.ticker.read();
                // 获取缓存值,判断是否过期,过期删除缓存并返回null
                V value = getLiveValue(e, now); // 拿到没过期存活的数据
                if (value != null) {
                    // 记录访问时间
                    recordRead(e, now);
                    // 记录缓存命中一次
                    statsCounter.recordHits(1);
                    // 刷新缓存并返回缓存值
                    return scheduleRefresh(e, key, hash, value, now, loader);
                }
                // 走到这步,说明取出来的值 value == null, 可能是过期了,也有可能正在刷新
                ValueReference<K, V> valueReference = e.getValueReference();
                // 如果此时value正在loading,那么此时等待刷新结果
                if (valueReference.isLoading()) {
                    return waitForLoadingValue(e, key, valueReference);
                }
            }
        }
        // 走到这说明值为null或者过期,需要加锁进行加载
        return lockedGetOrLoad(key, hash, loader);
    } catch (ExecutionException ee) {
        Throwable cause = ee.getCause();
        if (cause instanceof Error) {
            throw new ExecutionError((Error) cause);
        } else if (cause instanceof RuntimeException) {
            throw new UncheckedExecutionException(cause);
        }
        throw ee;
    } finally {
        postReadCleanup();
    }
}

3、最佳实践

3.1、防御性编程:参数校验

scss 复制代码
java
checkNotNull(key);   // 防御空指针
checkNotNull(loader);

借鉴点

  • 业务场景:任何方法的入口参数校验(如订单ID不能为空)

  • 实现方式

    less 复制代码
    java
    // Spring 风格示例
    public Order getOrder(@NotNull String orderId) {
        Assert.notNull(orderId, "Order ID must not be null");
        // ...
    }
  • 价值:避免后续逻辑因空值崩溃,提前暴露问题。

3.2、分段锁优化并发

scss 复制代码
java
lockedGetOrLoad(key, hash, loader); // 分段锁控制

借鉴点

  • 业务场景:高并发访问共享资源(如库存扣减、配置热更新)

  • 实现方式

    csharp 复制代码
    java
    // 使用 ConcurrentHashMap 的分段锁思想
    private final Striped<Lock> locks = Striped.lock(32); // 分32个锁段
    
    public void updateProductStock(String productId) {
        Lock lock = locks.get(productId.hashCode());
        lock.lock();
        try {
            // 更新库存...
        } finally {
            lock.unlock();
        }
    }
  • 价值:减少锁竞争,提升并发吞吐量。


3.3、双重检查锁定(DCL)

scss 复制代码
java
if (count != 0) {          // 第一次检查(无锁)
    e = getEntry(key, hash);
    if (e != null) {       // 第二次检查(加锁后)
        // ...
    }
}

借鉴点

  • 业务场景:单例模式、缓存加载(如全局配置初始化)

  • 实现方式

    arduino 复制代码
    java
    public class ConfigLoader {
        private volatile Config config;
    
        public Config getConfig() {
            if (config == null) {              // 第一次检查
                synchronized (this) {
                    if (config == null) {      // 第二次检查
                        config = loadFromDB();
                    }
                }
            }
            return config;
        }
    }
  • 价值:避免重复初始化,保证线程安全的同时减少锁开销。


3.4、异步刷新机制

scss 复制代码
java
scheduleRefresh(e, key, hash, value, now, loader); // 后台刷新

借鉴点

  • 业务场景:热点数据自动刷新(如商品价格、实时榜单)

  • 实现方式

    typescript 复制代码
    java
    // 使用 CompletableFuture 异步更新
    public class ProductCache {
        private ConcurrentMap<String, Product> cache = new ConcurrentHashMap<>();
    
        public Product getProduct(String id) {
            return cache.compute(id, (k, v) -> {
                if (v == null || v.isExpired()) {
                    return loadProduct(id); // 同步加载
                } else {
                    CompletableFuture.runAsync(() -> {
                        Product newVersion = reloadProduct(id);
                        cache.put(id, newVersion);
                    });
                    return v; // 返回旧值,异步更新
                }
            });
        }
    }
  • 价值:用户无感知刷新,避免阻塞请求。


3.5、失效策略与惰性清理

scss 复制代码
java
getLiveValue(e, now); // 检查过期
postReadCleanup();    // 读后清理(惰性)

借鉴点

  • 业务场景:缓存过期策略(如用户会话30分钟失效)

  • 实现方式

    csharp 复制代码
    java
    // 自定义带过期时间的缓存
    public class ExpiringCache<K, V> {
        private class Entry {
            V value;
            long expireTime;
        }
    
        private ConcurrentHashMap<K, Entry> map = new ConcurrentHashMap<>();
    
        public V get(K key) {
            Entry entry = map.get(key);
            if (entry != null && System.currentTimeMillis() < entry.expireTime) {
                return entry.value;
            } else {
                map.remove(key);
                return null;
            }
        }
    }
  • 价值:无需定时任务扫描,按需清理节省资源。


3.6、等待正在进行的加载

scss 复制代码
java
waitForLoadingValue(e, key, valueReference); // 等待其他线程加载

借鉴点

  • 业务场景:防止重复加载(如数据库查询防穿透)

  • 实现方式

    ini 复制代码
    java
    public class LoadingCache<K, V> {
        private ConcurrentMap<K, Future<V>> futures = new ConcurrentHashMap<>();
    
        public V get(K key) throws Exception {
            Future<V> future = futures.get(key);
            if (future == null) {
                FutureTask<V> task = new FutureTask<>(() -> loadFromDB(key));
                future = futures.putIfAbsent(key, task);
                if (future == null) {
                    task.run();
                }
            }
            return future.get();
        }
    }
  • 价值:避免多个线程同时加载同一资源。


3.7、统计与监控

scss 复制代码
java
statsCounter.recordHits(1); // 记录命中

借鉴点

  • 业务场景:服务监控(如接口调用次数、缓存命中率)

  • 实现方式

    java 复制代码
    java
    // 使用 Micrometer 指标库
    public class OrderService {
        private final Counter cacheHits = Metrics.counter("order.cache.hits");
        private final Counter cacheMiss = Metrics.counter("order.cache.miss");
    
        public Order getOrder(String id) {
            Order order = cache.get(id);
            if (order != null) {
                cacheHits.increment();
            } else {
                cacheMiss.increment();
            }
            return order;
        }
    }
  • 价值:通过监控数据优化系统性能。


3.8、异常处理规范化

arduino 复制代码
java
catch (ExecutionException ee) {
    // 统一异常转换
}

借鉴点

  • 业务场景:统一异常处理(如将底层异常转换为业务异常)

  • 实现方式

    csharp 复制代码
    java
    try {
        // 调用第三方接口
    } catch (IOException e) {
        throw new BusinessException("网络异常,请重试", e);
    } catch (SQLException e) {
        throw new BusinessException("数据库错误", e);
    }
  • 价值:避免底层异常扩散到上层,提高可维护性。


3.9、引用类型管理

ini 复制代码
java
ValueReference<K, V> valueReference = e.getValueReference();

借鉴点

  • 业务场景:内存敏感缓存(如图片缩略图缓存)

  • 实现方式

    csharp 复制代码
    java
    // 使用软引用缓存大对象
    public class ImageCache {
        private Map<String, SoftReference<BufferedImage>> cache = new HashMap<>();
    
        public BufferedImage getImage(String path) {
            SoftReference<BufferedImage> ref = cache.get(path);
            BufferedImage img = ref != null ? ref.get() : null;
            if (img == null) {
                img = loadImage(path);
                cache.put(path, new SoftReference<>(img));
            }
            return img;
        }
    }
  • 价值:在内存不足时自动释放,防止 OOM。


3.10、时间源抽象

arduino 复制代码
java
long now = map.ticker.read(); // 可替换的时间源

借鉴点

  • 业务场景:单元测试时间模拟(如优惠券过期测试)

  • 实现方式

    csharp 复制代码
    java
    public interface Clock {
        long currentTimeMillis();
    }
    
    public class SystemClock implements Clock {
        public long currentTimeMillis() {
            return System.currentTimeMillis();
        }
    }
    
    // 测试时替换为固定时间
    public class FixedClock implements Clock {
        private final long time;
        public FixedClock(long time) { this.time = time; }
        public long currentTimeMillis() { return time; }
    }
  • 价值:提高代码可测试性。


3.11、模板方法模式

整体 get 方法的结构体现了模板模式:

  1. 参数校验 → 2. 尝试获取 → 3. 加载/等待 → 4. 异常处理 → 5. 清理
    借鉴点
  • 业务场景:统一流程控制(如订单创建流程)

  • 实现方式

    scss 复制代码
    java
    public abstract class OrderCreator {
        public final Order createOrder(OrderRequest request) {
            validateRequest(request);     // 1. 校验
            checkInventory(request);      // 2. 库存检查
            Order order = buildOrder(request); // 3. 构造订单
            saveToDB(order);              // 4. 持久化
            sendEvent(order);             // 5. 发事件
            return order;
        }
    
        protected abstract void sendEvent(Order order);
    }
  • 价值:规范流程,子类只需实现特定步骤。


3.12、线程安全与可见性

通过 volatilesynchronized 等保证状态可见性。
借鉴点

  • 业务场景:多线程共享标志位(如服务开关)

  • 实现方式

    typescript 复制代码
    java
    public class FeatureToggle {
        private volatile boolean newFeatureEnabled = false;
    
        public void enableFeature() {
            newFeatureEnabled = true; // 对其他线程立即可见
        }
    
        public boolean isEnabled() {
            return newFeatureEnabled;
        }
    }
  • 价值:避免因可见性问题导致逻辑错误。


4、总结

这些设计原则可应用于以下业务场景:

  1. 高并发接口:分段锁 + 异步刷新
  2. 缓存系统:失效策略 + 双重检查锁定
  3. 资源管理:软/弱引用 + 惰性清理
  4. 监控系统:统计计数器 + 统一异常
  5. 流程控制:模板方法 + 防御性校验

核心思想:​在保证正确性的前提下,通过减少锁粒度、异步化、惰性处理等手段提升性能。开发中应根据具体场景灵活组合这些模式。

相关推荐
Stark、8 分钟前
【MySQL】多表查询(笛卡尔积现象,联合查询、内连接、左外连接、右外连接、子查询)-通过练习快速掌握法
数据库·后端·sql·mysql
Asthenia04121 小时前
Spring编程式事务全解析:从DataSource到TxManager再到TxTemplate
后端
Moment1 小时前
如果你想找国外远程,首先让我先给你推荐这几个流行的技术栈 🤪🤪🤪
前端·后端·github
Ttang232 小时前
SpringBoot(4)——SpringBoot自动配置原理
java·开发语言·spring boot·后端·spring·自动配置·原理
Asthenia04122 小时前
Spring声明式事务失效场景分析与总结
后端
Asthenia04122 小时前
Spring七种声明式事务传播机制深度解析:内外层行为与异常处理
后端
努力的小雨2 小时前
行业案例分享:汽车售后智能助手
后端
GoGeekBaird3 小时前
69天探索操作系统-第53天:高级分布式操作系统算法和共识协议
后端·操作系统
小杨4044 小时前
springboot框架项目实践应用八(validation自定义校验)
spring boot·后端·架构