笔记系列之Guava Cache缓存

Guava Cache简介

看pom依赖其实就知道Guava是Google提供的一套Java工具包,而且Guava在本地缓存这个领域上的机制是比较完善的一套缓存技术,他是基于JVM的缓存,大多数的缓存都是基于CurrentHashMap的,Guava也不例外,所以Guava的性能也是特别的好

讲一下JVM缓存

JVM缓存就是堆缓存,说实在的就是创建了一些全局的容器,这些容器是专门用来存储数据的,比如List、Set这些集合类型一样


Guava Cache应用场景

主要是应对下面几种场景:

  • 对性能有非常高要求的
  • 不经常有数据变化的
  • 占用内存不大的
  • 有访问整个集合需求的
  • 数据不要求时时一致的

几个缓存场景对比:

  • Guava Cache:高并发,不需要持久化
  • currentHashMap:高并发
  • EhCached:持久化,二级缓存

Guava Cache的优势

  • 缓存过期和淘汰机制
  • 并发处理能力:线程安全,提供了并发级别的API,支持并发的读写
  • 更新锁定:可以通过CacheLoader的load方法控制同一个key,只能让一个请求去处理
  • 集成数据源:在下面的代码示例中的get方法中,可以走数据源去查询数据
  • 监控缓存加载/命中情况

Guava Cache缓存的创建

创建有两种方式:CacheLoader和Callable Callback


    com.google.guava
    guava
    29.0-jre

Guava Cache的整个生命周期的代码如下:

public class GuavaDemo {


    public static void main(String args[]) throws Exception {
        LoadingCache cache = CacheBuilder.newBuilder()
                .maximumSize(3)//缓存最大个数
                .expireAfterWrite(3, TimeUnit.SECONDS)//三秒内没有访问就删除,相当于redis的expire
                .recordStats()
                .removalListener(
                        new RemovalListener() {
                            public void onRemoval(RemovalNotification notification) {
                                System.out.println(notification.getKey() + ":" + notification.getCause());
                            }
                        })
                .build(new CacheLoader() {
                    //采用CacheLoader来获取数据,当缓存不存在的时候会自动加载数据到缓存
                    @Override
                    public String load(String s) throws Exception {
                        return Constants.hm.get(s);
                    }
                });
        initCache(cache);
        System.out.println(cache.size());//缓存当前数据量
        displayCache(cache);//遍历缓存
        System.out.println("=============================");
        /*自动过期(1)
        Thread.sleep(1000);//停1s访问,缓存还未过期,可以查出值
        //读取缓存中key是1的数据,如果有就返回,没有就是null
        System.out.println(cache.getIfPresent("1"));
        Thread.sleep(2500);//上面的1s+2.5s>上面规定的过期时间3s,所以所有的缓存将会被清空
        System.out.println("=============================");
        displayCache(cache);//上面已经大于规定过期时间了,所以这个会返回一个空
        System.out.println("=============================");
        */

        /*移除(2)
        cache.invalidate("1");//将1删除
        displayCache(cache);
        */

        /*批量移除
        cache.invalidateAll(Arrays.asList("1","2"));//将1,2删除
        displayCache(cache);
        */

        /*清空所有
        cache.invalidateAll();//清空所有
        displayCache(cache);
        */

        /*
        get("4",cache);//读取4,但是把最前面的那一个key给替换掉
        displayCache(cache);
        */

    }

    /**
     * @Description: Callable Callback
     */
    public static Object get(String key, LoadingCache cache) throws Exception {
        Object value = cache.get(key, new Callable() {
            @Override
            public Object call() throws Exception {
                Object v = Constants.hm.get(key); //设置回缓存
                cache.put(key, v);
                return v;
            }
        });
        return value;
    }

    /**
     * @Description: 初始化三条缓存数据
     */
    public static void initCache(LoadingCache cache) throws Exception {
        /* 前三条记录 */
        for (int i = 1; i <= 3; i++) {
            cache.get(String.valueOf(i));
        }
    }

    /**
     * @Description: 遍历缓存
     */
    public static void displayCache(LoadingCache cache) throws Exception {
        Iterator its = cache.asMap().entrySet().iterator();
        while (its.hasNext()) {
            System.out.println(its.next().toString());
        }
    }
}

class Constants {
    public static Map hm=new HashMap<>();

    static {
        hm.put("1","吴京");
        hm.put("2","李晨");
        hm.put("3","韩东君");
        hm.put("4","易烊千玺");
    }
}

上面关于缓存的添加、查询、遍历、过期删、手动删等操作都在案例中,自己可以复制粘贴体验一下


Guava Cache原理

其实和ConcurrentHashMap类似,只不过ConcurrentHashMap能一直保存添加的元素,知道显式的被移除,而Guava Cache为了限制内存占用,通常都设定为自动回收元素,看上面的代码示例就知道了,我们来看一下Guava的数据结构:在网上找的相对靠谱全面的结构图

笔记系列之Guava Cache缓存_第1张图片

我们分析一波:

  • 图中上方的LocalCache是Guava的核心类,这类包含了一个Segment数组,这个数组决定了cache的并发数
  • 每个Segment都有单独的锁,源码中能看出Segment其实集成了ReentrantLock,所有的写操作都要先拿到锁在操作
  • 看图中,每个Segment由一个引用实体数组table和五个Queue队列组成,我们分别介绍一下这五个队列:
  1. ReferenceQueue key:已经被GC了,需要内部清理的键引用队列
  2. ReferenceQueue value:已经被GC了,需要内部清理的值引用队列
  3. ConcurrentlinkedQueue> recencyQueue : LRU 队列,当 segment 上达到临界值发生写操作时该队列会移除已有的数据
  4. Queue> writeQueue 写队列,按照写入时间进行排序的元素队列,写入一个元素时会把它加入到队列尾部
  5. Queue> accessQueue 访问队列,按照访问时间进行排序的元素队列,访问( 包括写入 ) 一个元素时会把它加入到队列尾部
  • table:AtomicReferenceArray> tableAtomicReferenceArray可以用原子方式更新其元素的对象引用数组
  • ReferenceEntry ReferenceEntry是 Guava Cache 中对一个键值对节点的抽象,每个 ReferenceEntry 数组项都是一 条ReferenceEntry 链。并且一个 ReferenceEntry 包含 key hash valueReference next 字段 (单链) Guava Cache使用 ReferenceEntry 接口来封装一个键值对,而用 ValueReference 来封装 Value

Guava Cache回收机制

其提供了三种基本的缓存回收方式:

  • 基于容器回收:在缓存项的数目达到限定值前,采用LRU的收回方式
  • 基于引用回收:通过使用弱引用的键或弱引用的值或软引用的值,可以垃圾回收
  • 定时回收: 
  1. expireAfterAccess:缓存项在给定时间内没有被操作,就回收掉,回收顺序和基于大小回收一致(LRU)
  2. expireAfterWrite:缓存项在给定时间内没有被写访问,就回收

你可能感兴趣的:(笔记/经验,缓存)