guava之LoadingCache

LoadingCache 是guava cache的一个接口,是缓存的本地实现,通过建造者模式实现。
创建LoadingCache

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .expireAfterWrite(10, TimeUnit.MINUTES)
       .removalListener(MY_LISTENER)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

通过CacheBuilder类的maximumSize方法指定Cache最多可以存储1000个对象,然后调用Cache的put方法向其中添加了第1001个对象时,导致了第一条对象记录被删除。

get方法
当第一次调用 get() 方法时,如果 value 不存在则会触发 load() 方法,load 方法不能返回 null,否则会报错。
最正统的查询 LoadingCache 的方法是调用 get(k) 方法,这个方法如果查询到已经缓存的值会立即返回,否则使用缓存的 CacheLoader 自动加载一个新值到缓存并返回。因为 CacheLoader 可能会抛出异常,那么如果有异常,则LoadingCache.get(k) 会抛出 ExecutionException 异常。而如果 CacheLoader 抛出 unchecked 未检查的异常,则 get(k) 方法会抛出 UncheckedExecutionException 异常。

removalListener方法
通过CacheBuilder.removalListener(RemovalListener),可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知RemovalNotification,其中包含移除原因[RemovalCause]、键和值。

优点

  • 线程安全的缓存,与ConcurrentMap相似,但前者增加了更多的元素失效策略,后者只能显示的移除元素。
  • 提供了三种基本的缓存回收方式:基于容量回收定时回收和基于引用回收。定时回收有两种:按照写入时间,最早写入的最先回收;按照访问时间,最早访问的最早回收。
  • 监控缓存加载/命中情况。
  • 集成了多部操作,调用get方式,可以在未命中缓存的时候,从其他地方获取数据源(DB,redis),并加载到缓存中。

缺点
超时机制不是精确的。

public static void main(String[] args) throws ExecutionException, InterruptedException{
        //缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存
        LoadingCache<Integer,Student> studentCache
                //CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
                = CacheBuilder.newBuilder()
                //设置并发级别为8,并发级别是指可以同时写缓存的线程数
                .concurrencyLevel(8)
                //设置写缓存后8秒钟过期
                .expireAfterWrite(8, TimeUnit.SECONDS)
                //设置缓存容器的初始容量为10
                .initialCapacity(10)
                //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
                .maximumSize(100)
                //设置要统计缓存的命中率
                .recordStats()
                //设置缓存的移除通知
                .removalListener(new RemovalListener<Object, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
                        System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
                    }
                })
                //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
                .build(
                        new CacheLoader<Integer, Student>() {
                            @Override
                            public Student load(Integer key) throws Exception {
                                System.out.println("load student " + key);
                                Student student = new Student();
                                student.setId(key);
                                student.setName("name " + key);
                                return student;
                            }
                        }
                );

        for (int i=0;i<20;i++) {
            //从缓存中得到数据,由于我们没有设置过缓存,所以需要通过CacheLoader加载缓存数据
            Student student = studentCache.get(1);
            System.out.println(student);
            //休眠1秒
            TimeUnit.SECONDS.sleep(1);
        }

        System.out.println("cache stats:");
        //最后打印缓存的命中率等 情况
        System.out.println(studentCache.stats().toString());
    }

输出结果

cache stats:
CacheStats{hitCount=17, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=1348802, evictionCount=2}

原因:看看到在20此循环中命中次数是17次,未命中3次,这是因为我们设定缓存的过期时间是写入后的8秒,所以20秒内会失效两次,另外第一次获取时缓存中也是没有值的,所以才会未命中3次,其他则命中。

常用方法

  • V getIfPresent(Object key) 获取缓存中key对应的value,如果缓存没命中,返回null。
  • V get(K key) throws ExecutionException 获取key对应的value,若缓存中没有,则调用LocalCache的load方法,从数据源中加载,并缓存。
  • void put(K key, V value) 如果缓存有值,覆盖,否则,新增
  • void putAll(Map m);循环调用单个的方法
  • void invalidate(Object key); 删除缓存
  • void invalidateAll(); 清楚所有的缓存,相当远map的clear操作。
  • long size(); 获取缓存中元素的大概个数。为什么是大概呢?元素失效之时,并不会实时的更新size,所以这里的size可能会包含失效元素。
  • CacheStats stats(); 缓存的状态数据,包括(未)命中个数,加载成功/失败个数,总共加载时间,删除个数等。
  • asMap()方法获得缓存数据的ConcurrentMap快照
  • cleanUp()清空缓存
  • refresh(Key) 刷新缓存,即重新取缓存数据,更新缓存
  • ImmutableMap getAllPresent(Iterable keys) 一次获得多个键的缓存值

核心类

  • CacheBuilder:类,缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。
    CacheBuilder在build方法中,会把前面设置的参数,全部传递给LocalCache,它自己实际不参与任何计算。这种初始化参数的方法值得借鉴,代* 码简洁易读。
  • CacheLoader:抽象类。用于从数据源加载数据,定义load、reload、loadAll等操作。
  • Cache:接口,定义get、put、invalidate等操作,这里只有缓存增删改的操作,没有数据加载的操作。
  • AbstractCache:抽象类,实现Cache接口。其中批量操作都是循环执行单次行为,而单次行为都没有具体定义。
  • LoadingCache:接口,继承自Cache。定义get、getUnchecked、getAll等操作,这些操作都会从数据源load数据。
  • AbstractLoadingCache:抽象类,继承自AbstractCache,实现LoadingCache接口。
  • LocalCache:类。整个guava cache的核心类,包含了guava cache的数据结构以及基本的缓存的操作方法。
  • LocalManualCache:LocalCache内部静态类,实现Cache接口。
    其内部的增删改缓存操作全部调用成员变量 localCache(LocalCache类型)的相应方法。
  • LocalLoadingCache:LocalCache内部静态类,继承自LocalManualCache类,实现- - LoadingCache接口。
    其所有操作也是调用成员变量localCache(LocalCache类型)的相应方法。
  • CacheStats:缓存加载/命中统计信息。
    个人工具类方法,仅供参考
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;

public class CacheUtil {

    private static final Logger logger = LoggerFactory.getLogger(CacheUtil.class);
    private static final RedisUtil redisUtil = RedisUtil.getInstance();

    /**
     * load方式缓存
     * 
     * @param key
     * @param value
     */
    public static void putByLoad(String key, Object value) {
        String valStr = JSON.toJSONString(value);
        logger.info("[CacheUtil] putByLoad, key -> " + key + ", value -> " + valStr);
        CacheUtil.getInstanceForLoad().put(key, valStr);
        redisUtil.set(key, valStr);
    }

    /**
     * load方式读取单个bean
     * 
     * @param key
     * @param t
     * @return
     * @throws Exception
     */
    public static <T> T getObjectByLoad(String key, Class<T> t) {
        if (key == null)
            return null;
        String object = null;
        try {
            object = CacheUtil.getInstanceForLoad().get(key);
            logger.info("[CacheUtil] get from cache, key -> " + key + ", value -> " + object);
        } catch (Exception e) {
            logger.error("[CacheUtil] getByLoad error -> " + e);
        }
        if (object == null)
            return null;
        return (T) JSON.parseObject(object, t);
    }

    /**
     * load方式读取list
     * 
     * @param key
     * @param t
     * @return
     */
    public static <T> List<T> getListByLoad(String key, Class<T> t) {
        if (key == null)
            return null;
        String object = null;
        try {
            object = CacheUtil.getInstanceForLoad().get(key);
        } catch (Exception e) {
            logger.error("[CacheUtil] getByLoad error -> " + e);
        }
        if (object == null)
            return null;
        return JSON.parseArray(object, t);
    }

    /**
     * load方式读取map
     * 
     * @param key
     * @param t
     * @return
     */
    public static <T> Map<String, T> getMapByLoad(String key, Class<T> t) {
        if (key == null)
            return null;
        String object = null;
        try {
            object = CacheUtil.getInstanceForLoad().get(key);
            logger.info("[CacheUtil] get from cache, key -> " + key + ", value -> " + object);
        } catch (Exception e) {
            logger.error("[CacheUtil] getByLoad error -> " + e);
        }
        if (object == null)
            return null;
        Map<String, T> map = JSONObject.parseObject(object, new TypeReference<Map<String, T>>() {
        });
        for (String k : map.keySet()) {
            T o = JSON.parseObject(map.get(k).toString(), t);
            map.put(k, o);
        }
        return map;
    }

    /**
     * map方式缓存
     * 
     * @param key
     * @param value
     */
    public static void putByMap(String key, Object value) {
        CacheUtil.getInstanceForMap().put(key, JSON.toJSONString(value));
    }

    /**
     * map方式读取
     * 
     * @param key
     * @param t
     * @return
     */
    public static <T> T getByMap(String key, Class<T> t) {
        Object object = CacheUtil.getInstanceForMap().getIfPresent(key);
        if (object == null)
            return null;
        return (T) JSON.parseObject((String) object, t);
    }

    public static LoadingCache<String, String> getInstanceForLoad() {
        return SingleTonForLoad.cahceBuilder;
    }

    private static class SingleTonForLoad {
        private static LoadingCache<String, String> cahceBuilder = CacheBuilder.newBuilder()
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String key) throws Exception {
                        redisUtil.expire(key, 0);
                        String re = redisUtil.get(key);
                        logger.info("[CacheUtil] get from redis, key -> " + key + ", value -> " + re);
                        return re;
                    }
                });
    }

    public static Cache<String, Object> getInstanceForMap() {
        return SingleTonForMap.cache;
    }

    private static class SingleTonForMap {
        private static Cache<String, Object> cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES)
                .build();
    }

    public static void main(String[] args) {
        Map<String, CompetitionBean> map1 = Maps.newHashMap();
        CompetitionBean bean1 = new CompetitionBean();
        bean1.setCategory("足球");
        map1.put("1", bean1);
        CacheUtil.putByLoad("a", map1);
        System.err.println(JSON.toJSONString(CacheUtil.getMapByLoad("a", CompetitionBean.class)));

        Map<String, CompetitionBean> map2 = Maps.newHashMap();
        CompetitionBean bean2 = new CompetitionBean();
        bean2.setCategory("篮球");
        map2.put("2", bean2);
        CacheUtil.putByLoad("a", map2);
        System.err.println(JSON.toJSONString(CacheUtil.getMapByLoad("a", CompetitionBean.class)));
    }
}

load()和loadAll()的作用:

参考博文,点击这里
在每次从cache中get(K)时,如果不存在会自动调用load方法原子的将值计算出来并加到缓存中。(调用load方法是同步的)

getAll(Iterable<? extendsK>)方法用来执行批量查询注意:getAll()方法也是guava cache中的方法。默认情况下,对每个不在缓存中的键,getAll方法会单独调用CacheLoader.load来加载缓存项。如果批量的加载比多个单独加载更高效,可以重载CacheLoader.loadAll来利用这一点提示getAll(Iterable)的性能。看一个例子:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值