maven依赖
-
- <dependency>
- <groupId>com.google.guavagroupId>
- <artifactId>guavaartifactId>
- <version>28.2-jreversion>
- dependency>
GuavaCache有两种创建方式:CacheLoader和Callable callback
- package com.lg.guava.demo.guava;
-
- import com.google.common.cache.CacheBuilder;
- import com.google.common.cache.CacheLoader;
- import com.google.common.cache.LoadingCache;
- import com.lg.guava.demo.Constants;
-
- import java.util.Iterator;
- import java.util.concurrent.Callable;
- import java.util.concurrent.ExecutionException;
-
-
- public class Demo1 {
- /**
- * 初始化缓存三个
- */
- public static void initCache(LoadingCache
cache) throws Exception { - for(int i=1;i<=3;i++){
- //连接数据源 如果缓存没有则读取数据源
- cache.get(String.valueOf(i));
- }
-
- }
-
- /**
- * 显示缓存里的数据
- * @param cache
- */
- public static void display(LoadingCache
cache) { - //利用迭代器
- Iterator its=cache.asMap().entrySet().iterator();
- while(its.hasNext()){
- System.out.println(its.next().toString());
- }
- }
-
- /**
- * 读取缓存数据 如果没有则回调源数据并(自动)写入缓存
- * @param key
- * @param cache
- */
- public static void get(String key,LoadingCache
cache) throws Exception { - cache.get(key, new Callable
-
- @Override //回调方法用于读源并写入缓存
- public Object call() throws Exception {
- //读源
- Object value=Constants.hm.get(key);
- //写回缓存
- // cache.put(key,value);
- return value;
- }
- });
- }
-
- public static void main(String[] args) throws Exception {
- //CacheLoader的方式创建
- LoadingCache
cache= CacheBuilder.newBuilder().build(new CacheLoader() { -
- //读取数据源
- @Override
- public Object load(String key) throws Exception {
- return Constants.hm.get(key);
- }
- });
- //初始化缓存
- initCache(cache);
- System.out.println(cache.size());
- //显示缓存数据
- display(cache);
- //读取4
- get("4",cache);
- System.out.println("==================================");
- display(cache);
- System.out.println("==================================");
- display(cache);
- }
- }
GuavaCache的数据删除分为:被动删除和主动删除
- LoadingCache
cache= CacheBuilder.newBuilder() - /*
- 加附加的功能
- */
- //最大个数
- .maximumSize(3)
- .build(new CacheLoader
() { - //读取数据源
- @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);
- package com.lg.guava.demo.guava;
-
- import com.google.common.cache.*;
- import com.lg.guava.demo.Constants;
-
- import java.util.Iterator;
- import java.util.concurrent.Callable;
- import java.util.concurrent.TimeUnit;
-
-
- public class Demo2 {
- /**
- * 初始化缓存三个
- */
- public static void initCache(LoadingCache
cache) throws Exception { - for (int i = 1; i <= 3; i++) {
- //连接数据源 如果缓存没有则读取数据源
- cache.get(String.valueOf(i));
- }
-
- }
-
- /**
- * 显示缓存里的数据
- *
- * @param cache
- */
- public static void display(LoadingCache
cache) { - //利用迭代器
- Iterator its = cache.asMap().entrySet().iterator();
- while (its.hasNext()) {
- System.out.println(its.next().toString());
- }
- }
-
- /**
- * 读取缓存数据 如果没有则回调源数据并(自动)写入缓存
- *
- * @param key
- * @param cache
- */
- public static void get(String key, LoadingCache
cache) throws Exception { - cache.get(key, new Callable
-
- @Override //回调方法用于读源并写入缓存
- public Object call() throws Exception {
- //读源
- Object value = Constants.hm.get(key);
- //写回缓存
- // cache.put(key,value);
- return value;
- }
- });
- }
-
- public static void main(String[] args) throws Exception {
- //CacheLoader的方式创建
- LoadingCache
cache = CacheBuilder.newBuilder() - /*
- 加附加的功能
- */
- //最大个数
- .maximumSize(3)
- .concurrencyLevel(Runtime.getRuntime().availableProcessors())
- .refreshAfterWrite(1, TimeUnit.SECONDS)
- //统计命中率
- .recordStats()
- //移除通知
- .removalListener(new RemovalListener
-
- @Override
- public void onRemoval(RemovalNotification {
- //移除的key 移除的原因
- System.out.println(removalNotification.getKey() + ":" + removalNotification.getCause());
- }
- })
- .build(new CacheLoader
() { -
- //读取数据源
- @Override
- public Object load(String key) throws Exception {
- return Constants.hm.get(key);
- }
- });
- //初始化缓存
- initCache(cache);
- System.out.println(cache.size());
- //显示缓存数据
- display(cache);
- //读取缓存中的1的数据 缓存有就读取 没有就返回null
- System.out.println(cache.getIfPresent("1"));
-
- //读取4 读源并回写缓存 淘汰一个(LRU+FIFO)
- get("4", cache);
- System.out.println("==================================");
- display(cache);
-
- //打印输出统计
- System.out.println(cache.stats().toString()+", " + cache.stats().hitRate());
-
- }
- }
隔多长时间后没有被访问过的key被删除
.maximumSize(3).expireAfterAccess(3, TimeUnit.SECONDS)
写入多长时间后过期
//等同于expire ttl 缓存中对象的生命周期就是3秒
.maximumSize(3).expireAfterWrite(3, TimeUnit.SECONDS)
- package com.lg.guava.demo.guava;
-
- import com.google.common.cache.*;
- import com.lg.guava.demo.Constants;
-
- import java.util.Iterator;
- import java.util.concurrent.Callable;
- import java.util.concurrent.TimeUnit;
-
-
- public class Demo2 {
- /**
- * 初始化缓存三个
- */
- public static void initCache(LoadingCache
cache) throws Exception { - for (int i = 1; i <= 3; i++) {
- //连接数据源 如果缓存没有则读取数据源
- cache.get(String.valueOf(i));
- }
-
- }
-
- /**
- * 显示缓存里的数据
- *
- * @param cache
- */
- public static void display(LoadingCache
cache) { - //利用迭代器
- Iterator its = cache.asMap().entrySet().iterator();
- while (its.hasNext()) {
- System.out.println(its.next().toString());
- }
- }
-
- /**
- * 读取缓存数据 如果没有则回调源数据并(自动)写入缓存
- *
- * @param key
- * @param cache
- */
- public static void get(String key, LoadingCache
cache) throws Exception { - cache.get(key, new Callable
-
- @Override //回调方法用于读源并写入缓存
- public Object call() throws Exception {
- //读源
- Object value = Constants.hm.get(key);
- //写回缓存
- // cache.put(key,value);
- return value;
- }
- });
- }
-
- public static void main(String[] args) throws Exception {
- //CacheLoader的方式创建
- LoadingCache
cache = CacheBuilder.newBuilder() - /*
- 加附加的功能
- */
- //最大个数
- .maximumSize(3)
- .concurrencyLevel(Runtime.getRuntime().availableProcessors())
- .refreshAfterWrite(1, TimeUnit.SECONDS)
- //统计命中率
- .recordStats()
- //移除通知
- .removalListener(new RemovalListener
-
- @Override
- public void onRemoval(RemovalNotification {
- //移除的key 移除的原因
- System.out.println(removalNotification.getKey() + ":" + removalNotification.getCause());
- }
- })
- .build(new CacheLoader
() { -
- //读取数据源
- @Override
- public Object load(String key) throws Exception {
- return Constants.hm.get(key);
- }
- });
- //初始化缓存
- initCache(cache);
- System.out.println(cache.size());
- //显示缓存数据
- display(cache);
- //读取缓存中的1的数据 缓存有就读取 没有就返回null
- System.out.println(cache.getIfPresent("1"));
-
- //读取4 读源并回写缓存 淘汰一个(LRU+FIFO)
- get("4", cache);
- System.out.println("==================================");
- display(cache);
-
- //打印输出统计
- System.out.println(cache.stats().toString()+", " + cache.stats().hitRate());
-
- }
- }
- package com.lg.guava.demo.guava;
-
- import com.google.common.cache.CacheBuilder;
- import com.google.common.cache.CacheLoader;
- import com.google.common.cache.LoadingCache;
- import com.lg.guava.demo.Constants;
-
- import java.util.Iterator;
- import java.util.concurrent.Callable;
- import java.util.concurrent.TimeUnit;
-
-
- public class Demo3 {
- /**
- * 初始化缓存三个
- */
- public static void initCache(LoadingCache
cache) throws Exception { - for(int i=1;i<=3;i++){
- //连接数据源 如果缓存没有则读取数据源
- cache.get(String.valueOf(i));
- }
-
- }
-
- /**
- * 显示缓存里的数据
- * @param cache
- */
- public static void display(LoadingCache
cache) { - //利用迭代器
- Iterator its=cache.asMap().entrySet().iterator();
- while(its.hasNext()){
- System.out.println(its.next().toString());
- }
- }
-
- /**
- * 读取缓存数据 如果没有则回调源数据并(自动)写入缓存
- * @param key
- * @param cache
- */
- public static void get(String key,LoadingCache
cache) throws Exception { - cache.get(key, new Callable
-
- @Override //回调方法用于读源并写入缓存
- public Object call() throws Exception {
- //读源
- Object value=Constants.hm.get(key);
- //写回缓存
- // cache.put(key,value);
- return value;
- }
- });
- }
-
- public static void main(String[] args) throws Exception {
- //CacheLoader的方式创建
- LoadingCache
cache= CacheBuilder.newBuilder() - /*
- 加附加的功能
- */
- //等同于expire ttl 缓存中对象的生命周期就是3秒
- .maximumSize(3).expireAfterWrite(3, TimeUnit.SECONDS)
- .build(new CacheLoader
() { -
- //读取数据源
- @Override
- public Object load(String key) throws Exception {
- return Constants.hm.get(key);
- }
- });
- //初始化缓存
- initCache(cache);
- System.out.println(cache.size());
- //显示缓存数据
- display(cache);
- Thread.sleep(1000);
- //访问1
- cache.getIfPresent("1");
-
- //歇了2.1秒
- Thread.sleep(2100);
-
- System.out.println("==================================");
- display(cache);
-
- }
- }
可以通过weakKeys和weakValues方法指定Cache只保存对缓存记录key和value的弱引用。这样当没有其他强引用指向key和value时,key和value对象就会被垃圾回收器回收。
LoadingCache
cache = CacheBuilder.newBuilder() weakValues()
// 最大3个 值的弱引用
.maximumSize(3).
.build(
);
- package com.lg.guava.demo.guava;
-
- import com.google.common.cache.CacheBuilder;
- import com.google.common.cache.CacheLoader;
- import com.google.common.cache.LoadingCache;
- import com.lg.guava.demo.Constants;
-
- import java.util.Iterator;
- import java.util.concurrent.Callable;
- import java.util.concurrent.TimeUnit;
-
-
- public class Demo4 {
- /**
- * 初始化缓存三个
- */
- public static void initCache(LoadingCache
cache) throws Exception { - for (int i = 1; i <= 3; i++) {
- //连接数据源 如果缓存没有则读取数据源
- cache.get(String.valueOf(i));
- }
-
- }
-
- /**
- * 显示缓存里的数据
- *
- * @param cache
- */
- public static void display(LoadingCache
cache) { - //利用迭代器
- Iterator its = cache.asMap().entrySet().iterator();
- while (its.hasNext()) {
- System.out.println(its.next().toString());
- }
- }
-
- /**
- * 读取缓存数据 如果没有则回调源数据并(自动)写入缓存
- *
- * @param key
- * @param cache
- */
- public static void get(String key, LoadingCache
cache) throws Exception { - cache.get(key, new Callable
-
- @Override //回调方法用于读源并写入缓存
- public Object call() throws Exception {
- //读源
- Object value = Constants.hm.get(key);
- //写回缓存
- // cache.put(key,value);
- return value;
- }
- });
- }
-
- public static void main(String[] args) throws Exception {
- //CacheLoader的方式创建
- LoadingCache
cache = CacheBuilder.newBuilder() - /*
- 加附加的功能
- */
- //弱值的删除
- .maximumSize(3).weakValues()
- .build(new CacheLoader
() { -
- //读取数据源
- @Override
- public Object load(String key) throws Exception {
- return Constants.hm.get(key);
- }
- });
- //初始化缓存
- initCache(cache);
- System.out.println(cache.size());
- //显示缓存数据
- display(cache);
-
- Object v = new Object();
- cache.put("1", v);
-
- v = new Object(); //原对象不再有强引用
- //强制垃圾回收
- System.gc();
- System.out.println("================================");
- display(cache);
-
-
- }
- }
// 将key=1 删除
cache.invalidate("1");
// 将key=1和2的删除
cache.invalidateAll(Arrays.asList("1","2"));
// 清空缓存
cache.invalidateAll();
- package com.lg.guava.demo.guava;
-
- import com.google.common.cache.CacheBuilder;
- import com.google.common.cache.CacheLoader;
- import com.google.common.cache.LoadingCache;
- import com.lg.guava.demo.Constants;
-
- import java.util.Arrays;
- import java.util.Iterator;
- import java.util.concurrent.Callable;
-
- /**
- * 主动删除
- */
- public class Demo5 {
- /**
- * 初始化缓存三个
- */
- public static void initCache(LoadingCache
cache) throws Exception { - for (int i = 1; i <= 3; i++) {
- //连接数据源 如果缓存没有则读取数据源
- cache.get(String.valueOf(i));
- }
-
- }
-
- /**
- * 显示缓存里的数据
- *
- * @param cache
- */
- public static void display(LoadingCache
cache) { - //利用迭代器
- Iterator its = cache.asMap().entrySet().iterator();
- while (its.hasNext()) {
- System.out.println(its.next().toString());
- }
- }
-
- /**
- * 读取缓存数据 如果没有则回调源数据并(自动)写入缓存
- *
- * @param key
- * @param cache
- */
- public static void get(String key, LoadingCache
cache) throws Exception { - cache.get(key, new Callable
-
- @Override //回调方法用于读源并写入缓存
- public Object call() throws Exception {
- //读源
- Object value = Constants.hm.get(key);
- //写回缓存
- // cache.put(key,value);
- return value;
- }
- });
- }
-
- public static void main(String[] args) throws Exception {
- //CacheLoader的方式创建
- LoadingCache
cache = CacheBuilder.newBuilder() - /*
- 加附加的功能
- */
- //弱值的删除
- .maximumSize(3).weakValues()
- .build(new CacheLoader
() { -
- //读取数据源
- @Override
- public Object load(String key) throws Exception {
- return Constants.hm.get(key);
- }
- });
- //初始化缓存
- initCache(cache);
- System.out.println(cache.size());
- //显示缓存数据
- display(cache);
- System.out.println("================================");
- //清空
- // cache.invalidateAll();
- //删除指定key
- // cache.invalidate("1");
- //删多个
- cache.invalidateAll(Arrays.asList("1","3"));
- display(cache);
-
-
- }
- }
- ReferenceQueue keyReferenceQueue : 已经被GC,需要内部清理的键引用队列
- ReferenceQueue valueReferenceQueue : 已经被GC,需要内部清理的值引用队列
- ConcurrentlinkedQueue
> recencyQueue : LRU队列,当segment上达到临界值发生写操作时该队列会移除数据 - Queue
> writeQueue:写队列,按照写入时间进行排序的元素队列,写入一个元素时会把它加入到队列尾部 - Queue
> accessQueue:访问队列,按照访问时间进行排序的元素队列,访问(包括写入)一个元素时会把它加入到队列尾部
AtomicReferenceArray
> table:AtomicReferenceArray可以用原子方式更新其元素的对象引用数组
- ReferenceEntry是Guava Cache中对一个键值对节点的抽象,每个ReferenceEntry数组项都是一条ReferenceEntry链。并且一个ReferenceEntry包含key、hash、valueReference、next字段(单链)
- Guava Cache使用ReferenceEntry接口来封装一个键值对,而用ValueReference来封装Value值
Guava Cache提供了三种基本的缓存回收方式:
基于容量回收:在缓存项的数目达到限定值之前,采用LRU的回收方式
定时回收:
基于引用回收:通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以垃圾回收
除了以上三种还有主动删除,采用命令,这个前面讲了
- 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);
- }
再找到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
e = getEntry(key, hash); - ......
GuavaCache通过设置 concurrencyLevel 使得缓存支持并发的写入和读取
- LoadingCache
cache = CacheBuilder.newBuilder() - // 最大3个 同时支持CPU核数线程写缓存
- .maximumSize(3).concurrencyLevel(Runtime.getRuntime().availableProcessors()).build();
concurrencyLevel=Segment数组的长度
同ConcurrentHashMap类似Guava cache的并发也是通过分离锁实现
- V get(K key, CacheLoader super K, V> loader) throws ExecutionException {
- int hash = this.hash(Preconditions.checkNotNull(key));
- //通过hash值确定该key位于哪一个segment上,并获取该segment
- return this.segmentFor(hash).get(key, hash, loader);
- }
LoadingCache采用了类似ConcurrentHashMap的方式,将映射表分为多个segment。segment之间可以并发访问,这样可以大大提高并发的效率,使得并发冲突的可能性降低了。
- LoadingCache
cache = CacheBuilder.newBuilder() - // 最大3个 同时支持CPU核数线程写缓存
- .maximumSize(3).concurrencyLevel(Runtime.getRuntime().availableProcessors())
- //3秒内阻塞会返回旧数据
- .refreshAfterWrite(3,TimeUnit.SECONDS).build();
动态加载行为发生在获取不到数据或者是数据已经过期的时间点,Guava动态加载使用回调模式用户自定义加载方式,然后Guava cache在需要加载新数据时会回调用户的自定义加载方式
segmentFor(hash).get(key, hash, loader)
loader即为用户自定义的数据加载方式,当某一线程get不到数据会去回调该自定义加载方式去加载数据
- package com.lg.guava.demo.guava.lru;
-
- import java.util.LinkedHashMap;
- import java.util.Map;
-
- public class LRUcache
extends LinkedHashMap { - private final int limit;
-
- public LRUcache(int limit) {
- //初始化 accessOrder : true 改变尾结点
- super(16, 0.75f, true);
- this.limit = limit;
- }
-
- //是否删除最老的数据
- @Override
- protected boolean removeEldestEntry(Map.Entry
eldest) { - return size() > limit;
- }
-
-
- }
- package com.lg.guava.demo.guava.lru;
-
- public class LinkedHashLRUcache
{ - /**
- * LinkedHashMap(自身实现了LRU算法)
- * 有序
- * 每次访问一个元素,都会加到尾部
- */
-
-
- int limit;
- LRUcache
lruCache; -
-
- public LinkedHashLRUcache(int limit) {
-
- this.limit = limit;
- this.lruCache = new LRUcache(limit);
- }
-
-
- public void put(k key, v value) {
-
- this.lruCache.put(key, value);
- }
-
- public v get(k key) {
-
- return this.lruCache.get(key);
- }
-
-
- public static void main(String[] args) {
- LinkedHashLRUcache lru = new LinkedHashLRUcache(3);
- lru.put(1, "zhangfei1");
- lru.put(2, "zhangfei2");
- lru.put(3, "zhangfei3");
- lru.get(1);
- lru.put(4, "zhangfei4");
- for (Object o : lru.lruCache.values()) {
- System.out.println(o.toString());
- }
- }
- }
-
(1)GuavaCache会oom(内存溢出)吗
会,当我们设置缓存永不过期(或者很长),缓存的对象不限个数(或者很大)时,比如:
- Cache
cache = CacheBuilder.newBuilder() - .expireAfterWrite(100000, TimeUnit.SECONDS)
- .build();
不断向GuavaCache加入大字符串,最终将会oom
解决方案:缓存时间设置相对小些,使用弱引用方式存储对象
- Cache
cache = CacheBuilder.newBuilder() - .expireAfterWrite(1, TimeUnit.SECONDS)
- .weakValues().build();
(2)GuavaCache缓存到期就会立即清除吗
不是的,GuavaCache是在每次进行缓存操作的时候,如get()或者put()的时候,判断缓存是否过期
- void evictEntries(ReferenceEntry
e) { - drainRecencyQueue();
- while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) {
- if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
- throw new AssertionError();
- }
- }
- while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) {
- if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
- throw new AssertionError();
- }
- }
- }
一个如果一个对象放入缓存以后,不在有任何缓存操作(包括对缓存其他key的操作),那么该缓存不会主动过期的。
GuavaCache源码剖析之实现框架
GuavaCache体系类图:
(1)缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。
(2)主要采用builder的模式,CacheBuilder的每一个方法都返回这个CacheBuilder知道build方法的调用。注意build方法有重载,带有参数的为构建一个具有数据加载功能的缓存,不带参数的构建一个没有数据加载功能的缓存。
(1)LoadingCache这些类表示获取Cache的方式,可以有多种方式,但是它们的方法最终调用到LocalCache的方法,LocalCache是Guava Cache的核心类。
(2)LocalCache为Guava Cache的核心类 LocalCache的数据结构与ConcurrentHashMap很相似,都由多个segment组成,且各segment相对独立,互不影响,所以能支持并行操作。
(3)每个segment由一个table和若干队列组成。缓存数据存储在table中,其类型为AtomicReferenceArray。