Guva是google開源的一個公共java庫,相似於Apache Commons,它提供了集合,反射,緩存,科學計算,xml,io等一些工具類庫。
cache只是其中的一個模塊。使用Guva cache可以方便快速的構建本地緩存。
[TOC]html
首先須要在maven項目中加入guava依賴
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.0-jre</version> </dependency>
使用Guava建立一個緩存
// 經過CacheBuilder構建一個緩存實例 Cache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) // 設置緩存的最大容量 .expireAfterWrite(1, TimeUnit.MINUTES) // 設置緩存在寫入一分鐘後失效 .concurrencyLevel(10) // 設置併發級別爲10 .recordStats() // 開啓緩存統計 .build(); // 放入緩存 cache.put("key", "value"); // 獲取緩存 String value = cache.getIfPresent("key");
expireAfterWrite 緩存必定時間內直接失效
expireAfterAccess 緩存被訪問後,必定時間後失效
getIfPresent 不存在就返回null
代碼演示了使用Guava建立了一個基於內存的本地緩存,並指定了一些緩存的參數,如緩存容量、緩存過時時間、併發級別等,隨後經過put方法放入一個緩存並使用getIfPresent來獲取它。
Cache是經過CacheBuilder的build()方法構建,它是Gauva提供的最基本的緩存接口,而且它提供了一些經常使用的緩存api:
Cache<Object, Object> cache = CacheBuilder.newBuilder().build(); // 放入/覆蓋一個緩存 cache.put("k1", "v1"); // 獲取一個緩存,若是該緩存不存在則返回一個null值 Object value = cache.getIfPresent("k1"); // 獲取緩存,當緩存不存在時,則通Callable進行加載並返回。該操做是原子 Object getValue = cache.get("k1", new Callable<Object>() { @Override public Object call() throws Exception { return null; } });
LoadingCache繼承自Cache,在構建LoadingCache時,須要經過CacheBuilder的build(CacheLoader<? super K1, V1> loader)方法構建
CacheBuilder.newBuilder() .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { // 緩存加載邏輯 ... } });
它可以經過CacheLoader自發的加載緩存
LoadingCache<Object, Object> loadingCache = CacheBuilder.newBuilder().build(new CacheLoader<Object, Object>() { @Override public Object load(Object key) throws Exception { return null; } }); // 獲取緩存,當緩存不存在時,會經過CacheLoader自動加載,該方法會拋出ExecutionException異常 loadingCache.get("k1"); // 以不安全的方式獲取緩存,當緩存不存在時,會經過CacheLoader自動加載,該方法不會拋出異常 loadingCache.getUnchecked("k1");
Guava提供了設置併發級別的api,使得緩存支持併發的寫入和讀取。同ConcurrentHashMap相似Guava cache的併發也是經過分離鎖實現。在通常狀況下,將併發級別設置爲服務器cpu核心數是一個比較不錯的選擇。
CacheBuilder.newBuilder() // 設置併發級別爲cpu核心數 .concurrencyLevel(Runtime.getRuntime().availableProcessors()) .build();
咱們在構建緩存時能夠爲緩存設置一個合理大小初始容量,因爲Guava的緩存使用了分離鎖的機制,擴容的代價很是昂貴。因此合理的初始容量可以減小緩存容器的擴容次數。
CacheBuilder.newBuilder() // 設置初始容量爲100 .initialCapacity(100) .build();
使用基於最大容量的的回收策略時,咱們須要設置2個必要參數:
這裏咱們例舉一個key和value都是String類型緩存:
CacheBuilder.newBuilder() .maximumWeight(1024 * 1024 * 1024) // 設置最大容量爲 1M // 設置用來計算緩存容量的Weigher .weigher(new Weigher<String, String>() { @Override public int weigh(String key, String value) { return key.getBytes().length + value.getBytes().length; } }).build();
當緩存的最大數量/容量逼近或超過咱們所設置的最大值時,Guava就會使用LRU算法對以前的緩存進行回收。
基於引用的回收策略,是java中獨有的。在java中有對象自動回收機制,依據程序員建立對象的方式不一樣,將對象由強到弱分爲強引用、軟引用、弱引用、虛引用。對於這幾種引用他們有如下區別
強引用java
強引用是使用最廣泛的引用。若是一個對象具備強引用,那垃圾回收器毫不會回收它。
Object o=new Object(); // 強引用
當內存空間不足,垃圾回收器不會自動回收一個被引用的強引用對象,而是會直接拋出OutOfMemoryError錯誤,使程序異常終止。
軟引用程序員
相對於強引用,軟引用是一種不穩定的引用方式,若是一個對象具備軟引用,當內存充足時,GC不會主動回收軟引用對象,而當內存不足時軟引用對象就會被回收。
SoftReference<Object> softRef=new SoftReference<Object>(new Object()); // 軟引用 Object object = softRef.get(); // 獲取軟引用
使用軟引用能防止內存泄露,加強程序的健壯性。可是必定要作好null檢測。
弱引用算法
弱引用是一種比軟引用更不穩定的引用方式,由於不管內存是否充足,弱引用對象都有可能被回收。
WeakReference<Object> weakRef = new WeakReference<Object>(new Object()); // 弱引用 Object obj = weakRef.get(); // 獲取弱引用
虛引用api
而虛引用這種引用方式就是形同虛設,由於若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣。在實踐中也幾乎沒有使用。在Guava cache中支持,軟/弱引用的緩存回收方式。使用這種方式可以極大的提升內存的利用率,而且不會出現內存溢出的異常。緩存
CacheBuilder.newBuilder() .weakKeys() // 使用弱引用存儲鍵。當鍵沒有其它(強或軟)引用時,該緩存可能會被回收。 .weakValues() // 使用弱引用存儲值。當值沒有其它(強或軟)引用時,該緩存可能會被回收。 .softValues() // 使用軟引用存儲值。當內存不足而且該值其它強引用引用時,該緩存就會被回收 .build();
經過軟/弱引用的回收方式,至關於將緩存回收任務交給了GC,使得緩存的命中率變得十分的不穩定,在非必要的狀況下,仍是推薦基於數量和容量的回收。
在緩存構建完畢後,咱們能夠經過Cache提供的接口,顯式的對緩存進行回收,例如:
// 構建一個緩存 Cache<String, String> cache = CacheBuilder.newBuilder().build(); // 回收key爲k1的緩存 cache.invalidate("k1"); // 批量回收key爲k一、k2的緩存 List<String> needInvalidateKeys = new ArrayList<>(); needInvalidateKeys.add("k1"); needInvalidateKeys.add("k2"); cache.invalidateAll(needInvalidateKeys); // 回收全部緩存 cache.invalidateAll();
Guava也提供了緩存的過時策略和刷新策略。
緩存的過時策略分爲固定時間和相對時間。固定時間通常是指寫入後多長時間過時,例如咱們構建一個寫入10分鐘後過時的緩存:安全
CacheBuilder.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) // 寫入10分鐘後過時 .build(); // java8後可使用Duration設置 CacheBuilder.newBuilder() .expireAfterWrite(Duration.ofMinutes(10)) .build();
相對時間通常是相對於訪問時間,也就是每次訪問後,會從新刷新該緩存的過時時間,這有點相似於servlet中的session過時時間,例如構建一個在10分鐘內未訪問則過時的緩存:
CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES) //在10分鐘內未訪問則過時 .build(); // java8後可使用Duration設置 CacheBuilder.newBuilder() .expireAfterAccess(Duration.ofMinutes(10)) .build();
在Guava cache中支持定時刷新和顯式刷新兩種方式,其中只有LoadingCache可以進行定時刷新。
定時刷新服務器
在進行緩存定時刷新時,咱們須要指定緩存的刷新間隔,和一個用來加載緩存的CacheLoader,當達到刷新時間間隔後,下一次獲取緩存時,會調用CacheLoader的load方法刷新緩存。例如構建個刷新頻率爲10分鐘的緩存:
CacheBuilder.newBuilder() // 設置緩存在寫入10分鐘後,經過CacheLoader的load方法進行刷新 .refreshAfterWrite(10, TimeUnit.SECONDS) // jdk8之後可使用 Duration // .refreshAfterWrite(Duration.ofMinutes(10)) .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { // 緩存加載邏輯 ... } });
顯式刷新session
在緩存構建完畢後,咱們能夠經過Cache提供的一些藉口方法,顯式的對緩存進行刷新覆蓋,例如:
// 構建一個緩存 Cache<String, String> cache = CacheBuilder.newBuilder().build(); // 使用put進行覆蓋刷新 cache.put("k1", "v1"); // 使用Map的put方法進行覆蓋刷新 cache.asMap().put("k1", "v1"); // 使用Map的putAll方法進行批量覆蓋刷新 Map<String,String> needRefreshs = new HashMap<>(); needRefreshs.put("k1", "v1"); cache.asMap().putAll(needRefreshs); // 使用ConcurrentMap的replace方法進行覆蓋刷新 cache.asMap().replace("k1", "v1");
對於LoadingCache,因爲它可以自動的加載緩存,因此在進行刷新時,不須要顯式的傳入緩存的值:
LoadingCache<String, String> loadingCache = CacheBuilder .newBuilder() .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { // 緩存加載邏輯 return null; } }); // loadingCache 在進行刷新時無需顯式的傳入 value loadingCache.refresh("k1");