摘要:前言在上文源碼分析原理中分析了的相關原理。我在北京模擬執行你在哪兒回復最后執行結果開始提問提問完畢,我去干其他事了收到消息你在哪兒等待響應中。。。。。回復我在北京這樣一個模擬的異步事件回調就完成了。
前言
在上文「Guava 源碼分析(Cache 原理)」中分析了 Guava Cache 的相關原理。
文末提到了回收機制、移除時間通知等內容,許多朋友也挺感興趣,這次就這兩個內容再來分析分析。
在開始之前先補習下 Java 自帶的兩個特性,Guava 中都有具體的應用。Java 中的引用
首先是 Java 中的引用。
在之前分享過 JVM 是根據可達性分析算法找出需要回收的對象,判斷對象的存活狀態都和引用有關。
在 JDK1.2 之前這點設計的非常簡單:一個對象的狀態只有引用和沒被引用兩種區別。
這樣的劃分對垃圾回收不是很友好,因為總有一些對象的狀態處于這兩之間。
因此 1.2 之后新增了四種狀態用于更細粒度的劃分引用關系:
強引用(Strong Reference):這種對象最為常見,比如 A a = new A();這就是典型的強引用;這樣的強引用關系是不能被垃圾回收的。
軟引用(Soft Reference):這樣的引用表明一些有用但不是必要的對象,在將發生垃圾回收之前是需要將這樣的對象再次回收。
弱引用(Weak Reference):這是一種比軟引用還弱的引用關系,也是存放非必須的對象。當垃圾回收時,無論當前內存是否足夠,這樣的對象都會被回收。
虛引用(Phantom Reference):這是一種最弱的引用關系,甚至沒法通過引用來獲取對象,它唯一的作用就是在被回收時可以獲得通知。
事件回調事件回調其實是一種常見的設計模式,比如之前講過的 Netty 就使用了這樣的設計。
這里采用一個 demo,試下如下功能:
Caller 向 Notifier 提問。
提問方式是異步,接著做其他事情。
Notifier 收到問題執行計算然后回調 Caller 告知結果。
在 Java 中利用接口來實現回調,所以需要定義一個接口:
public interface CallBackListener { /** * 回調通知函數 * @param msg */ void callBackNotify(String msg) ; }
Caller 中調用 Notifier 執行提問,調用時將接口傳遞過去:
public class Caller { private final static Logger LOGGER = LoggerFactory.getLogger(Caller.class); private CallBackListener callBackListener ; private Notifier notifier ; private String question ; /** * 使用 */ public void call(){ LOGGER.info("開始提問"); //新建線程,達到異步效果 new Thread(new Runnable() { @Override public void run() { try { notifier.execute(Caller.this,question); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); LOGGER.info("提問完畢,我去干其他事了"); } //隱藏 getter/setter }
Notifier 收到提問,執行計算(耗時操作),最后做出響應(回調接口,告訴 Caller 結果)。
public class Notifier { private final static Logger LOGGER = LoggerFactory.getLogger(Notifier.class); public void execute(Caller caller, String msg) throws InterruptedException { LOGGER.info("收到消息=【{}】", msg); LOGGER.info("等待響應中。。。。。"); TimeUnit.SECONDS.sleep(2); caller.getCallBackListener().callBackNotify("我在北京!"); } }
模擬執行:
public static void main(String[] args) { Notifier notifier = new Notifier() ; Caller caller = new Caller() ; caller.setNotifier(notifier) ; caller.setQuestion("你在哪兒!"); caller.setCallBackListener(new CallBackListener() { @Override public void callBackNotify(String msg) { LOGGER.info("回復=【{}】" ,msg); } }); caller.call(); }
最后執行結果:
2018-07-15 19:52:11.105 [main] INFO c.crossoverjie.guava.callback.Caller - 開始提問 2018-07-15 19:52:11.118 [main] INFO c.crossoverjie.guava.callback.Caller - 提問完畢,我去干其他事了 2018-07-15 19:52:11.117 [Thread-0] INFO c.c.guava.callback.Notifier - 收到消息=【你在哪兒!】 2018-07-15 19:52:11.121 [Thread-0] INFO c.c.guava.callback.Notifier - 等待響應中。。。。。 2018-07-15 19:52:13.124 [Thread-0] INFO com.crossoverjie.guava.callback.Main - 回復=【我在北京!】
這樣一個模擬的異步事件回調就完成了。
Guava 的用法Guava 就是利用了上文的兩個特性來實現了引用回收及移除通知。
引用可以在初始化緩存時利用:
CacheBuilder.weakKeys()
CacheBuilder.weakValues()
CacheBuilder.softValues()
來自定義鍵和值的引用關系。
在上文的分析中可以看出 Cache 中的 ReferenceEntry 是類似于 HashMap 的 Entry 存放數據的。
來看看 ReferenceEntry 的定義:
interface ReferenceEntry{ /** * Returns the value reference from this entry. */ ValueReference getValueReference(); /** * Sets the value reference for this entry. */ void setValueReference(ValueReference valueReference); /** * Returns the next entry in the chain. */ @Nullable ReferenceEntry getNext(); /** * Returns the entry"s hash. */ int getHash(); /** * Returns the key for this entry. */ @Nullable K getKey(); /* * Used by entries that use access order. Access entries are maintained in a doubly-linked list. * New entries are added at the tail of the list at write time; stale entries are expired from * the head of the list. */ /** * Returns the time that this entry was last accessed, in ns. */ long getAccessTime(); /** * Sets the entry access time in ns. */ void setAccessTime(long time); }
包含了很多常用的操作,如值引用、鍵引用、訪問時間等。
根據 ValueReference
具有強引用和弱引用的不同實現。
key 也是相同的道理:
當使用這樣的構造方式時,弱引用的 key 和 value 都會被垃圾回收。
當然我們也可以顯式的回收:
/** * Discards any cached value for key {@code key}. * 單個回收 */ void invalidate(Object key); /** * Discards any cached values for keys {@code keys}. * * @since 11.0 */ void invalidateAll(Iterable> keys); /** * Discards all entries in the cache. */ void invalidateAll();回調
改造了之前的例子:
loadingCache = CacheBuilder.newBuilder() .expireAfterWrite(2, TimeUnit.SECONDS) .removalListener(new RemovalListener
執行結果:
2018-07-15 20:41:07.433 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 當前緩存值=0,緩存大小=1 2018-07-15 20:41:07.442 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 緩存的所有內容={1000=0} 2018-07-15 20:41:07.443 [main] INFO c.crossoverjie.guava.CacheLoaderTest - job running times=10 2018-07-15 20:41:10.461 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 刪除原因=EXPIRED,刪除 key=1000,刪除 value=1 2018-07-15 20:41:10.462 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 當前緩存值=0,緩存大小=1 2018-07-15 20:41:10.462 [main] INFO c.crossoverjie.guava.CacheLoaderTest - 緩存的所有內容={1000=0}
可以看出當緩存被刪除的時候會回調我們自定義的函數,并告知刪除原因。
那么 Guava 是如何實現的呢?
根據 LocalCache 中的 getLiveValue() 中判斷緩存過期時,跟著這里的調用關系就會一直跟到:
removeValueFromChain() 中的:
enqueueNotification() 方法會將回收的緩存(包含了 key,value)以及回收原因包裝成之前定義的事件接口加入到一個本地隊列中。
這樣一看也沒有回調我們初始化時候的事件啊。
不過用過隊列的同學應該能猜出,既然這里寫入隊列,那就肯定就有消費。
我們回到獲取緩存的地方:
在 finally 中執行了 postReadCleanup() 方法;其實在這里面就是對剛才的隊列進行了消費:
一直跟進來就會發現這里消費了隊列,將之前包裝好的移除消息調用了我們自定義的事件,這樣就完成了一次事件回調。
總結以上所有源碼:
https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/guava/callback/Main.java
通過分析 Guava 的源碼可以讓我們學習到頂級的設計及實現方式,甚至自己也能嘗試編寫。
Guava 里還有很多強大的增強實現,值得我們再好好研究。
號外最近在總結一些 Java 相關的知識點,感興趣的朋友可以一起維護。
地址: https://github.com/crossoverJie/Java-Interview
歡迎關注公眾號一起交流:
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/62020.html
摘要:前言在上文源碼分析原理中分析了的相關原理。我在北京模擬執行你在哪兒回復最后執行結果開始提問提問完畢,我去干其他事了收到消息你在哪兒等待響應中。。。。。回復我在北京這樣一個模擬的異步事件回調就完成了。 showImg(https://segmentfault.com/img/remote/1460000015643387?w=2048&h=1150); 前言 在上文「Guava 源碼分析...
摘要:緩存本次主要討論緩存。清除數據時的回調通知。具體不在本次的討論范圍。應該是以下原因新起線程需要資源消耗。維護過期數據還要獲取額外的鎖,增加了消耗。 showImg(https://segmentfault.com/img/remote/1460000015272232); 前言 Google 出的 Guava 是 Java 核心增強的庫,應用非常廣泛。 我平時用的也挺頻繁,這次就借助日...
摘要:緩存本次主要討論緩存。清除數據時的回調通知。具體不在本次的討論范圍。應該是以下原因新起線程需要資源消耗。維護過期數據還要獲取額外的鎖,增加了消耗。 showImg(https://segmentfault.com/img/remote/1460000015272232); 前言 Google 出的 Guava 是 Java 核心增強的庫,應用非常廣泛。 我平時用的也挺頻繁,這次就借助日...
摘要:先簡單介紹一下愛奇藝的緩存道路的發展吧。可以看見圖中分為幾個階段第一階段數據同步加通過消息隊列進行數據同步至,然后應用直接去取緩存這個階段優點是由于是使用的分布式緩存,所以數據更新快。愛奇藝的緩存的發展也是基于此之上,通過對的二次開發 1.背景 本文是上周去技術沙龍聽了一下愛奇藝的Java緩存之路有感寫出來的。先簡單介紹一下愛奇藝的java緩存道路的發展吧。 showImg(https...
閱讀 2397·2021-10-09 09:44
閱讀 2132·2021-10-08 10:05
閱讀 3423·2021-07-26 23:38
閱讀 2991·2019-08-28 18:16
閱讀 812·2019-08-26 11:55
閱讀 1820·2019-08-23 18:29
閱讀 2034·2019-08-23 18:05
閱讀 1364·2019-08-23 17:02