Guava LoadCache缓存介绍和使用

一、Guava Cache介绍
1、JVM缓存
JVM 缓存,是堆缓存。其实就是创建一些全局容器,比如List、Set、Map等。
这些容器用来做数据存储。
这样做的问题:
不能按照一定的规则淘汰数据,如 LRU,LFU,FIFO 等。
清除数据时的回调通知
并发处理能力差,针对并发可以使用CurrentHashMap,但缓存的其他功能需要自行实现缓存过期处理,缓存数据加载刷新等都需要手工实现

2、Guava Cache
Guava是Google提供的一套Java工具包,而Guava Cache是一套非常完善的本地缓存机制(JVM缓存)。
Guava cache的设计来源于CurrentHashMap,可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。

二、Guava Cache应用场景
1、本地缓存的应用场景:
对性能有非常高的要求
不经常变化
占用内存不大
有访问整个集合的需求
数据允许不时时一致
2、Guava Cache 的优势
缓存过期和淘汰机制
在GuavaCache中可以设置Key的过期时间,包括访问过期和创建过期
GuavaCache在缓存容量达到指定大小时,采用LRU的方式,将不常使用的键值从Cache中删除
并发处理能力
GuavaCache类似CurrentHashMap,是线程安全的。
提供了设置并发级别的api,使得缓存支持并发的写入和读取
采用分离锁机制,分离锁能够减小锁力度,提升并发能力
分离锁是分拆锁定,把一个集合看分成若干partition, 每个partiton一把锁。ConcurrentHashMap就是分了16个区域,这16个区域之间是可以并发的。GuavaCache采用Segment做分区。
更新锁定
一般情况下,在缓存中查询某个key,如果不存在,则查源数据,并回填缓存。(Cache Aside Pattern)在高并发下会出现,多次查源并重复回填缓存,可能会造成源的宕机(DB),性能下降
GuavaCache可以在CacheLoader的load方法中加以控制,对同一个key,只让一个请求去读源并回填缓存,其他请求阻塞等待。
集成数据源
一般我们在业务中操作缓存,都会操作缓存和数据源两部分GuavaCache的get可以集成数据源,在从缓存中读取不到时可以从数据源中读取数据并回填缓存
监控缓存加载/命中情况
统计
三、Guava Cache创建方式
GuavaCache有两种创建方式:
CacheLoader和Callable callback

public class GuavaDemo {
    public static void main(String args[]) throws Exception {
        LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
                // 最大3个 //Cache中存储的对象,写入3秒后过期
                .maximumSize(3).expireAfterWrite(3,
                        //记录命中率 失效通知
                        TimeUnit.SECONDS).recordStats().removalListener(new
                        RemovalListener<Object, Object>() {
                            public void onRemoval(RemovalNotification<Object, Object>
                                                          notification) {
                                System.out.println(notification.getKey() + ":" + notification.getCause());
                            }
                        }.build(
                                new CacheLoader<String, Object>() {
                                    @Override
                                    public String load(String s) throws Exception {
                                        return Constants.hm.get(s);
                                    }
                                }
                        );
        /*
        初始化cache
        */
        initCache(cache);
        System.out.println(cache.size());
        displayCache(cache);
        System.out.println("=============================");
        Thread.sleep(1000);
        System.out.println(cache.getIfPresent("1"));
        Thread.sleep(2500);
        System.out.println("=============================");
        displayCache(cache);
    }

    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;
    }

    public static void initCache(LoadingCache cache) throws Exception {
        /*
        前三条记录
        */
        for (int i = 1; i <= 3; i++) {
            cache.get(String.valueOf(i));
        }
    } 
    /**
     *获得当前缓存的记录
    @param
    cache
    *@throws Exception
    **/

    public static void displayCache(LoadingCache cache) throws Exception {
        Iterator its = cache.asMap().entrySet().iterator();
        while (its.hasNext()) {
            System.out.println(its.next().toString());
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
1、CacheLoader
在创建cache对象时,采用CacheLoader来获取数据,当缓存不存在时能够自动加载数据到缓存中

LoadingCache<String,String> cache = CacheBuilder.newBuilder()
    .maximumSize(3)
    .build(
        new CacheLoader<String, String>() {
            @Override
            public String load(String s) throws Exception {
            return Constants.hm.get(s);
            }
        }
    );
1
2
3
4
5
6
7
8
9
10
2、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 {
            String v= Constants.hm.get(key);
            //设置回缓存
            cache.put(key,v);
            return v;
        }
    });
    return value;
}
1
2
3
4
5
6
7
8
9
10
11
12
三、缓存数据删除
GuavaCache的数据删除分为:被动删除和主动删除

1、被动删除
基于数据大小的删除
LoadingCache<String,Object> cache= CacheBuilder.newBuilder()
    /*
    加附加的功能
    */
    //最大个数
    .maximumSize(3)
    .build(new CacheLoader<String, Object>() {
        //读取数据源
        @Override
        public Object load(String key) throws Exception {
            return Constants.hm.get(key);
        }
    });
    //读取缓存中的1的数据 缓存有就读取 没有就返回null
    System.out.println(cache.getIfPresent("5"));
    //读取4 读源并回写缓存 淘汰一个(LRU+FIFO)
    get("4",cache);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
规则:LRU+FIFO
访问次数一样少的情况下,FIFO

基于过期时间的删除
隔多长时间后没有被访问过的key被删除
//缓存中的数据 如果3秒内没有访问则删除
.maximumSize(3).expireAfterAccess(3, TimeUnit.SECONDS)
。。。。
Thread.sleep(1000);
//访问1 1被访问
cache.getIfPresent("1");
//歇了2.1秒
Thread.sleep(2100);
//最后缓存中会留下1
System.out.println("==================================");
display(cache);
1
2
3
4
5
6
7
8
9
10
11
写入多长时间后过期

//等同于expire ttl 缓存中对象的生命周期就是3秒
.maximumSize(3).expireAfterWrite(3, TimeUnit.SECONDS)
.build(new CacheLoader<String, Object>() {
    //读取数据源
    @Override
    public Object load(String key) throws Exception {
        return Constants.hm.get(key);
    }
});
display(cache);
Thread.sleep(1000);
//访问1
cache.getIfPresent("1");
//歇了2.1秒
Thread.sleep(2100);
System.out.println("==================================");
display(cache);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
基于引用的删除
可以通过weakKeys和weakValues方法指定Cache只保存对缓存记录key和value的弱引用。这样当没有其他强引用指向key和value时,key和value对象就会被垃圾回收器回收
LoadingCache<String,Object> cache = CacheBuilder.newBuilder()
// 最大3个 值的弱引用
.maximumSize(3).weakValues()
.build();
Object value = new Object();
cache.put("1",value);
value = new Object();//原对象不再有强引用
//强制垃圾回收
System.gc();
System.out.println(cache.getIfPresent("1"));
1
2
3
4
5
6
7
8
9
10
2、主动删除
单独删除
//将key=1 删除
cache.invalidate("1");
1
2
批量删除
//将key=1和2的删除
cache.invalidateAll(Arrays.asList("1","2"));
1
2
清空所有数据
//清空缓存
cache.invalidateAll();
1
2
四、Guava Cache原理
1、GuavaCache核心原理之数据结构
Guava Cache的数据结构跟ConcurrentHashMap类似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。
相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。其数据结构图如下:


LocalCache为Guava Cache的核心类,包含一个Segment数组组成
Segement数组的长度决定了cache的并发数
每一个Segment使用了单独的锁,其实每个Segment继承了ReentrantLock,对Segment的写操作需要先拿到锁
每个Segment由一个table和5个队列组成
5个队列:
ReferenceQueue keyReferenceQueue : 已经被GC,需要内部清理的键引用队列
ReferenceQueue valueReferenceQueue : 已经被GC,需要内部清理的值引用队列
ConcurrentlinkedQueue<ReferenceEntry<k,v>> recencyQueue : LRU队列,当segment上达到临界值发生写操作时该队列会移除数据
Queue<ReferenceEntry<K, V>> writeQueue:写队列,按照写入时间进行排序的元素队列,写入一个元素时会把它加入到队列尾部
Queue<ReferenceEntry<K, V>> accessQueue:访问队列,按照访问时间进行排序的元素队列,访问(包括写入)一个元素时会把它加入到队列尾部
1个table:
AtomicReferenceArray<ReferenceEntry<K, V>> table:AtomicReferenceArray可以用原子方式更新其元素的对象引用数组
ReferenceEntry<k,v>
ReferenceEntry是Guava Cache中对一个键值对节点的抽象,每个ReferenceEntry数组项都是一条ReferenceEntry链。并且一个ReferenceEntry包含key、hash、valueReference、next字段
(单链)
Guava Cache使用ReferenceEntry接口来封装一个键值对,而用ValueReference来封装Value值
2、GuavaCache核心原理之回收机制
Guava Cache提供了三种基本的缓存回收方式:

基于容量回收
在缓存项的数目达到限定值之前,采用LRU的回收方式
定时回收
expireAfterAccess:缓存项在给定时间内没有被读/写访问,则回收。回收顺序和基于大小回收一样(LRU)
expireAfterWrite:缓存项在给定时间内没有被写访问(创建或覆盖),则回收
基于引用回收
通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以垃圾回收
除了以上三种还有主动删除,采用命令,上面已写过

GuavaCache构建的缓存不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。

GuavaCache是在每次进行缓存操作的时候,惰性删除 如get()或者put()的时候,判断缓存是否过期

3、GuavaCache核心原理之Segment定位
先通过key做hash定位到所在的Segment

通过位运算找首地址的偏移量 SegmentCount>=并发数且为2的n次方

V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    // 注意,key不可为空
    int hash = hash(checkNotNull(key));
    // 通过hash定位到segment数组的某个Segment元素,然后调用其get方法
    return segmentFor(hash).get(key, hash, loader);
}
1
2
3
4
5
6
再找到segment中的Entry链数组,通过key的hash定位到某个Entry节点

V get(K key, int hash, CacheLoader<? super K, V> loader) throws
ExecutionException {
    checkNotNull(key);
    checkNotNull(loader);
    try {
        if (count != 0) { // read-volatile
        // 内部也是通过找Entry链数组定位到某个Entry节点
        ReferenceEntry<K, V> e = getEntry(key, hash);
        ......
原文链接:https://blog.csdn.net/weixin_44795847/article/details/123702038

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Guava本地缓存是Google Guava提供的一个缓存框架,它可以帮助我们在应用程序中高效地缓存数据。使用Guava本地缓存需要以下步骤: 1. 导入Guava依赖 在项目中导入Guava依赖,可以通过Maven或Gradle等方式进行导入。 2. 创建缓存对象 使用CacheBuilder来创建一个缓存对象,可以设置缓存的大小、过期时间等参数,例如: ``` Cache<String, Object> cache = CacheBuilder.newBuilder() .maximumSize(100) .expireAfterAccess(10, TimeUnit.MINUTES) .build(); ``` 3. 将数据放入缓存 使用put方法将数据放入缓存中,例如: ``` cache.put("key1", "value1"); cache.put("key2", "value2"); ``` 4. 从缓存中获取数据 使用get方法从缓存中获取数据,例如: ``` Object value1 = cache.getIfPresent("key1"); Object value2 = cache.getIfPresent("key2"); ``` 5. 删除缓存中的数据 使用invalidate方法从缓存中删除数据,例如: ``` cache.invalidate("key1"); ``` 以上就是使用Guava本地缓存的基本步骤。需要注意的是,Guava本地缓存是线程安全的,可以在多线程环境中使用。同时,Guava本地缓存也提供了一些高级特性,例如缓存的回收策略、缓存的加载方式等,可以根据实际需求进行设置。 ### 回答2: Guava本地缓存是一种Google提供的使用简单且高效的缓存实现方式。下面是使用Guava本地缓存的步骤: 1. 首先,我们需要添加Guava库的依赖,确保项目中有Guava的jar包。 2. 创建一个缓存对象,可以使用CacheBuilder类的静态方法来创建一个新的缓存实例。例如: ```java Cache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) .expireAfterWrite(10, TimeUnit.MINUTES) .build(); ``` 上述代码创建了一个最大容量为100的缓存实例,并且设置了写入10分钟后过期。 3. 往缓存中存储数据,可以使用put方法将键值对存储到缓存中。例如: ```java cache.put("key1", "value1"); cache.put("key2", "value2"); ``` 上述代码将键值对(key1, value1)和(key2, value2)存储到缓存中。 4. 从缓存中获取数据,可以使用get方法根据键来获取对应的值。例如: ```java String value1 = cache.get("key1", () -> "default value"); ``` 上述代码尝试从缓存中获取键为key1的值,如果缓存中存在该键,则返回对应的值;如果缓存中不存在该键,则通过提供的回调函数生成一个默认值,并将该值存储到缓存中,然后返回该默认值。 5. 判断缓存中是否存在某个键,可以使用方法约束containsKey。例如: ```java boolean contains = cache.containsKey("key1"); ``` 上述代码判断缓存中是否存在键为key1的键值对。 6. 清空缓存中的所有数据,可以使用方法invalidateAll。例如: ```java cache.invalidateAll(); ``` 上述代码清空缓存中的所有键值对。 7. 在使用缓存后,为了释放资源,可以使用方法invalidate来显式地使缓存无效,并且不会对缓存中的数据造成影响。例如: ```java cache.invalidate("key1"); ``` 上述代码使键为key1的键值对无效,但不会删除缓存中的其他数据。 通过以上步骤,我们可以灵活使用Guava本地缓存来提升程序的性能和效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值