Guava Cache相关
官方:http://ifeve.com/google-guava-cachesexplained/
理解:https://segmentfault.com/a/1190000007300118
项目中用到cache的例子:
1 public class TokenCache { 2 3 //打印日志 4 private static Logger logger = LoggerFactory.getLogger(TokenCache.class); 5 6 public static final String TOKEN_PREFIX = "token_"; 7 8 //LRU算法 9 //initialCapacity(1000)设置cache的初始大小为1000 10 //maximumSize(10000)设置缓存个数为10000,当个数超过10000会利用LRU算法删除部分缓存 11 //expireAfterAccess(12, TimeUnit.HOURS)设置cache中的数据在写入之后的存活时间为12小时,TimeUnit.HOURS表示12的单位是小时 12 //key和value都是String类型 13 private static LoadingCache<String, String> localCache = CacheBuilder.newBuilder().initialCapacity(1000).maximumSize(10000).expireAfterAccess(12, TimeUnit.HOURS).build( 14 new CacheLoader<String, String>() { 15 //默认的数据加载实现,当调用get取值的时候,如果key没有对应的值,就调用这个方法进行加载 16 //当本地缓存命没有中时,调用load方法获取结果并将结果缓存 17 @Override 18 public String load(String s) throws Exception { 19 //因为key和value都是String类型,在下面调用getKey时返回的value值也是String类型,虽然有可能是null值但是也是string类型,所以这里返回"null"而不是null 20 return "null"; 21 } 22 } 23 ); 24 25 public static void setKey(String key, String value) { 26 localCache.put(key, value); 27 } 28 29 public static String getKey(String key) { 30 String value = null; 31 try { 32 value = localCache.get(key); 33 //这里用"null"字符串来进行判空 34 if("null".equals(value)) { 35 return null; 36 } 37 return value; 38 } catch (Exception e) { 39 logger.error("localCache get error", e); 40 } 41 return null; 42 } 43 }
存入cache:
//把token放入本地cache中,然后设置其有效期 TokenCache.setKey(TokenCache.TOKEN_PREFIX + username, forgetToken);
读出cache:
//从cache中获取token,根据key拿到value值 String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX + username); //校验cache中的token是否有效 if(StringUtils.isBlank(token)) { return ServerResponse.createByErrorMessage("token无效或者过期"); }
使用解释:
1 final static Cache<Integer, String> cache = CacheBuilder.newBuilder() 2 //设置cache的初始大小为10,要合理设置该值 3 .initialCapacity(10) 4 //设置并发数为5,即同一时间最多只能有5个线程往cache执行写入操作 5 .concurrencyLevel(5) 6 //设置cache中的数据在写入之后的存活时间为10秒 7 .expireAfterWrite(10, TimeUnit.SECONDS) 8 //构建cache实例 9 .build();
据说GuavaCache的实现是基于ConcurrentHashMap的,因此上面的构造过程所调用的方法,通过查看其官方文档也能看到一些类似的原理。比如通过initialCapacity(5)定义初始值大小,要是定义太大就好浪费内存空间,要是太小,需要扩容的时候就会像map一样需要resize,这个过程会产生大量需要gc的对象,还有比如通过concurrencyLevel(5)来限制写入操作的并发数,这和ConcurrentHashMap的锁机制也是类似的(ConcurrentHashMap读不需要加锁,写入需要加锁,每个segment都有一个锁)。
Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所添加的元素,直到显式的移除;Guava Cache为了限制内存的占用,通常都是设定为自动回收元素。在某些场景下,尽管LoadingCahe不回收元素,但是它还是很有用的,因为它会自动加载缓存。
Guava Cache适用场景:
- 你愿意消耗一部分内存来提升速度;
- 你已经预料某些值会被多次调用;
- 缓存数据不会超过内存总量;
Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。
接下来看看Cache提供哪些方法(只列了部分常用的):
1 /** 2 * 该接口的实现被认为是线程安全的,即可在多线程中调用 3 * 通过被定义单例使用 4 */ 5 public interface Cache<K, V> { 6 7 /** 8 * 通过key获取缓存中的value,若不存在直接返回null 9 */ 10 V getIfPresent(Object key); 11 12 /** 13 * 通过key获取缓存中的value,若不存在就通过valueLoader来加载该value 14 * 整个过程为 "if cached, return; otherwise create, cache and return" 15 * 注意valueLoader要么返回非null值,要么抛出异常,绝对不能返回null 16 */ 17 V get(K key, Callable<? extends V> valueLoader) throws ExecutionException; 18 19 /** 20 * 添加缓存,若key存在,就覆盖旧值 21 */ 22 void put(K key, V value); 23 24 /** 25 * 删除该key关联的缓存 26 */ 27 void invalidate(Object key); 28 29 /** 30 * 删除所有缓存 31 */ 32 void invalidateAll(); 33 34 /** 35 * 执行一些维护操作,包括清理缓存 36 */ 37 void cleanUp(); 38 }
清除缓存的策略
(1)个别清除:Cache.invalidate(key)
(2)批量清除:Cache.invalidateAll(keys)
(3)清除所有缓存项:Cache.invalidateAll()
4.基于引用的清除(Reference-based Eviction)
清除什么时候发生?
1 public class CacheService { 2 static Cache<Integer, String> cache = CacheBuilder.newBuilder() 3 .expireAfterWrite(5, TimeUnit.SECONDS) 4 .build(); 5 6 public static void main(String[] args) throws Exception { 7 new Thread() { //monitor 8 public void run() { 9 while(true) { 10 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); 11 System.out.println(sdf.format(new Date()) +" size: "+cache.size()); 12 try { 13 Thread.sleep(2000); 14 } catch (InterruptedException e) { 15 } 16 } 17 }; 18 }.start(); 19 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); 20 cache.put(1, "Hi"); 21 System.out.println("write key:1 ,value:"+cache.getIfPresent(1)); 22 Thread.sleep(10000); 23 // when write ,key:1 clear 24 cache.put(2, "bbb"); 25 System.out.println("write key:2 ,value:"+cache.getIfPresent(2)); 26 Thread.sleep(10000); 27 // when read other key ,key:2 do not clear 28 System.out.println(sdf.format(new Date()) 29 +" after write, key:1 ,value:"+cache.getIfPresent(1)); 30 Thread.sleep(2000); 31 // when read same key ,key:2 clear 32 System.out.println(sdf.format(new Date()) 33 +" final, key:2 ,value:"+cache.getIfPresent(2)); 34 } 35 }
控制台输出:
1 00:34:17 size: 0 2 write key:1 ,value:Hi 3 00:34:19 size: 1 4 00:34:21 size: 1 5 00:34:23 size: 1 6 00:34:25 size: 1 7 write key:2 ,value:bbb 8 00:34:27 size: 1 9 00:34:29 size: 1 10 00:34:31 size: 1 11 00:34:33 size: 1 12 00:34:35 size: 1 13 00:34:37 after write, key:1 ,value:null 14 00:34:37 size: 1 15 00:34:39 final, key:2 ,value:null 16 00:34:39 size: 0
(3)发生读操作cache.getIfPresent(1)后,缓存项<2,"bbb">没有被清除,因为还是size=1,看来读操作确实不一定会发生清除
总结