Guava LoadingCache的get和getIfPresent

        最近用到了Guava的 LoadingCache 作为本地缓存,发现这个类的两个方法 get  getIfPresent 于是写一篇博客记录下两者的不同。

        先说结论:我们在使用LoadingCache类的适合,builder中会传入一个CacheLoader,这个load方法是用来从别的地方取值保存在内存中的。

        使用get时,如果内存中没有值,会自动调用load方法,如果load方法返回的是null,那么get会抛出异常。

        使用getIfPresent时,如果内存中没有值,不会调用load方法,而是直接返回null。

        以下是用到的测试代码: 

        首先,定义了一个Repository类,这个类有个成员变量,就是我们的主角LoadingCache。这是一个很常见的LocalCache用法。主要讲下build中传入的CacheLoader。这边模拟了下从Redis中读取数据的过程。当这个load方法被调用时,去Redis中获取key对应的value。

        Redis中获取的时候,如果userId大于0,则返回用户+id,否则返回null

public class LocalCacheTest {
    private Repository repository = new Repository();

    @Test
    public void test() {
        Long id = 0L;
        try {
            get(id);
        } catch (Exception e) {
            System.out.println(e.getClass().getSimpleName());
        }

    }

    public void get(Long id) throws ExecutionException {
        String cacheValue = repository.getFromCache(id);
        if (StringUtils.isEmpty(cacheValue)) {
            System.out.println("value get failed");
        } else {
            System.out.println(
                    String.format("cache value: {%d} - {%s}",id, cacheValue));
        }
    }
}
public class Repository {
    private LoadingCache<Long, String> localCache = CacheBuilder.newBuilder()
            .maximumSize(CacheConstants.MAXIMUM_SIZE)
            .expireAfterAccess(CacheConstants.EXPIRE_TIME, TimeUnit.SECONDS)
            .removalListener(new RemovalListener<Long, String>() {
                @Override
                public void onRemoval(RemovalNotification<Long, String> rn) {
                    System.out.println(
                            String.format("[Repository] cache removed: {%d} - {%s}", rn.getKey(), rn.getValue()));
                }
            })
            .build(new CacheLoader<Long, String>() {
                @Override
                public String load(Long key)  {
                    System.out.println(String.format("[Repository][localCache] get from redis, key = {%d}", key));
                    return getFromRedisMock(key);
                }
            });

    public String getFromCache(Long id) throws ExecutionException {
        System.out.println(String.format("[Repository][getFromCache] get from cache, key = {%d}", id));
        // 使用get
        return localCache.get(id);
        // 使用getIfPresent
        // return localCache.getIfPresent(id);
    }

    public String getFromRedisMock(Long id) {
        if (id > 0) {
            return "用户" + id;
        } else {
            return null;
        }
    }
}

LoadingCache#get

        get在获取本地缓存时,如果值不存在,会主动调用上面提到的load方法,如果load成功得到值,那么会将该值保存在本地缓存中。如果load返回了null,则会抛出异常。

        测试代码如下

        可以看下实验结果,当我们第一次get时,内存里没有对应的值,自动调用load方法去Redis中取值。

        如果把userId改成0,那么Redis会返回null,此时get方法会抛出InvalidCacheLoadException异常。

 LoadingCache#getIfPresent

         接下来测试getIfPresent。把Repository类的getFromCache方法中的get改成getIfPresent后,得到结果如下图。可以发现,getIfPresent在内存中没有对应值的时候,不会去调用load方法,而是直接返回null。

### Guava LoadingCache 的 `get` 方法行为 Guava 提供的 `LoadingCache` 是一种高效的缓存实现,支持自动加载功能。当调用 `get(K key)` 或者批量获取方法 `getAll(Iterable<? extends K> keys)` 时,其核心逻辑如下: #### 单键获取 (`get(K key)`) 1. **缓存命中检查** 当调用 `cache.get(key)` 时,首先会尝试从缓存中查找指定的键是否存在以及对应的值是否有效。如果存在并有效,则直接返回该值[^1]。 2. **缓存未命中的处理** 如果缓存中不存在对应键或者值已过期/失效,则触发加载过程。具体来说: - 调用配置好的 `CacheLoader.load(K key)` 方法来动态计算或加载所需的值。 - 加载完成后,将新生成的值存储到缓存中以便后续使用,并将其作为当前请求的结果返回给调用方[^2]。 3. **线程安全与并发控制** 在多线程环境下,多个线程可能同时请求同一个尚未存在于缓存中的键。为了避免重复加载相同数据的情况发生,Guava 使用了一种称为“双重校验锁”的技术来确保只有一个线程负责实际加载操作,其他等待的线程则会被阻塞直到首次加载完成后再共享这个结果[^4]。 #### 批量获取 (`getAll(Iterable<? extends K> keys)`) 对于一批键集合的需求场景下,可以通过一次性的 `getAll()` 来高效地满足这些需求而不是逐个单独查询每一个key-value 对象实例化成本较高时尤其有用;它内部也是基于单个元素获取扩展而来的机制,在遇到缺失项时候同样遵循上述提到过的规则逐一补充完整再统一反馈出去. 需要注意的是关于性能方面考虑因素之一即为内存管理策略上可能会涉及到对象回收动作影响读写的吞吐率表现问题: ```java // 示例代码展示如何定义一个简单的 LoadingCache 并演示 get 方法的行为. import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; public class CacheExample { public static void main(String[] args) throws Exception { // 创建一个带有自定义装载器的 LoadingCache 实例 LoadingCache<String, String> cache = CacheBuilder.newBuilder() .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { System.out.println("Loading value for key: " + key); return "Value_for_" + key; // 模拟耗时业务逻辑 } }); // 首次访问某个不存在于缓存中的键 System.out.println(cache.getUnchecked("testKey")); // 再次访问相同的键 (应该来自缓存) System.out.println(cache.getIfPresent("testKey")); // 清理资源或其他必要操作... } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值