从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. 流程控制:模板方法 + 防御性校验

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

相关推荐
llz_1122 小时前
web-第二次课后作业
前端·后端·web
红尘散仙8 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记9 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆9 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪10 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball61610 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_25183645710 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao11 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒12 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰13 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理