Cglib中LoadingCache源码分析

  在实际的开发过程中,我们经常需要用到缓存。使用缓存常见的一个场景就是key不在缓存中,这个时候我们会去读取这个key对应的值,然后把这个值放到缓存中,代码如下:

public class CacheNoFuture {
    private ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();

    public Object get(String key) {
        Object o = cache.get(key);
        if (o == null) {
            o = readFromDB(key);
            cache.put(key, o);
        }
        return o;
    }
    private Object readFromDB(String key) {
        return new Object();
    }
复制代码

  这个代码有一个比较大的问题就是:如果同一时刻大量的请求发现o是空,都会去调用readFromDB,导致缓存被击穿了,可能的后果就是数据库直接被冲垮。理想的情况是同一个key同一时间只有一个thread去调用readFromDB,其他的thread等待它的结果。我们看一下Cglib包下面的LoadingCache是怎么做的。

1.代码位置

目前我使用的cglib的maven配置如下:

<dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
</dependency>
复制代码

LoadingCache代码在net.sf.cglib.core.internal包下面。

2.代码分析

  先看一下它的get方法,还是比较好理解的,有点不一样的是它判断了从map里面取到的内容是不是FutureTask,这个会在后面介绍。接下来我们看一下当从缓存里面读到的数据为空或者为FutureTask的时候,它做了什么。

public V get(K key) {
    final KK cacheKey = keyMapper.apply(key);
    Object v = map.get(cacheKey);
    if (v != null && !(v instanceof FutureTask)) {
        return (V) v;
    }
    return createEntry(key, cacheKey, v);
}
复制代码

  这段代码还是比较好理解的,我觉得理一下我标注的5行基本就差不多了。

  • line 1: 从 get 我们知道进入createEntry的条件是v不为空并且v不是FutureTask ,这一行判断v不为空,那只能说明v是FutureTask,所以把v赋值给task,表示目前已经有一个线程在加载数据了。
  • line 2: 很多线程正在竞争的去加载数据,但是只有putIfAbsent返回为空的那个成为creator
  • line 3、4: 结合line 2的解释,没有竞争成功的,会获得creatorFutureTask(加载没有完成)或者V(加载已经完成)
  • line 5:加载完成之后放回到cache里面,这也就是为什么有line 4的原因了。
protected V createEntry(final K key, KK cacheKey, Object v) {
        FutureTask<V> task;
        boolean creator = false;
        if (v != null) {                                    //line 1
            // Another thread is already loading an instance
            task = (FutureTask<V>) v;
        } else {
            task = new FutureTask<V>(new Callable<V>() {
                public V call() throws Exception {
                    return loader.apply(key);
                }
            });
            Object prevTask = map.putIfAbsent(cacheKey, task);
            if (prevTask == null) {                       //line 2
                // creator does the load
                creator = true;
                task.run();
            } else if (prevTask instanceof FutureTask) { //line 3
                task = (FutureTask<V>) prevTask;
            } else {                                    //line 4
                return (V) prevTask;
            }
        }

        V result;
        try {
            result = task.get();
        } catch (InterruptedException e) {
            throw new IllegalStateException("Interrupted while loading cache item", e);
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw ((RuntimeException) cause);
            }
            throw new IllegalStateException("Unable to load cache item", cause);
        }
        if (creator) {                                      //line 5
            map.put(cacheKey, result);
        }
        return result;
    }
复制代码

3.总结

  这种做法也有明显的不足:

  • 加载成功之后,key对应的值就不会再变了,即使我们数据源头发生了变化。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值