延迟加载原理与实现
一、延遲加載的定義與原理
延遲加載是開發過程中靈活獲取對象的一種求值策略,該策略在定義目標對象時并不會立即計算實際對象值,而是在該對象后續被實際調用時才去求值。在計算機科學中,延遲加載對應一個專門術語:惰性求值,其維基百科定義如下。
在編程語言理論中,惰性求值(Lazy Evaluation),又譯為惰性計算、懶惰求值,也稱為傳需求調用(call-by-need),是計算機編程中的一個概念,目的是要最小化計算機要做的工作。延遲求值特別用于匿名式函數編程,在使用延遲求值的時候,表達式不在它被綁定到變量之后就立即求值,而是在該值被取用的時候求值。
簡單來說,延遲加載就是指表達式只在必要時才求值,而非被賦給某個變量時立即求值。與惰性求值相對應的是及早求值,其維基百科定義如下。
及早求值(Eager evaluation)又譯熱切求值,也被稱為貪婪求值(Greedy evaluation),是多數傳統編程語言的求值策略。在及早求值中,表達式在它被約束到變量的時候就立即求值。這在簡單編程語言中作為低層策略是更有效率的,因為不需要建造和管理表示未求值的表達式的中介數據結構。
二、延遲加載的好處與應用場景
對于及早求值,對象在定義時便已獲取,若后續邏輯并未使用該對象,則會造成資源浪費,降低運行效率。而惰性求值會在數據真正被調用時去獲取,若后續未調用則不獲取,避免計算開銷,若后續調用多次,也可通過存儲計算結果的方式來實現結果復用,避免多次計算。延遲加載流程圖如下所示。
對于業務場景中計算開銷較大的數據對象,若其在后續邏輯中可能會根據不同的業務判斷條件在不同作用域中被使用多次,也可能一次都不會被使用到,那就可以使用延遲加載來獲取該對象。
如上述代碼舉例,Heavy為計算開銷較大的對象。
- 若采用立即加載,在某些特定業務條件下,conditionA與conditionB均為false,此時最終并未使用Heavy對象,則會造成資源浪費,若代碼改為在各自條件的作用域中單獨獲取Heavy對象,則有可能造成重復獲取,也會造成多余計算。
- 若采用延遲加載,當條件均不滿足時,不會調用Heavy對象,此時也沒有觸發真正加載操作,當存在條件滿足時,會在第一次調用對象時進行加載操作,后續如還需使用可復用該次加載結果,整個過程沒有資源浪費。
三、延遲加載的實現
Java語言中并沒有直接的延遲加載方法,但在Java8中引入的lambda表達式以及Supplier等函數式接口為我們實現延遲操作提供了很大的便捷性??梢酝ㄟ^增加一個間接層來實現,相當于使用Proxy模式,利用Supplier定義計算邏輯,把耗資源的運算過程放入Supplier的get方法中,并在Proxy對象中持有該Supplier實例,由Proxy對象來維護目標對象的實際加載與結果緩存,并確保安全性(如線程安全等)。一種可行的實現方案如下所示。
其中Lazy即為外層Proxy對象,內部持有Supplier實例delegate,通過volatile和雙重檢驗鎖實現目標對象的單例效果,保證最多只加載一次,實現結果緩存復用,并確保多線程并發環境下的訪問安全性。同時還提供了一系列便捷操作,如映射/扁平化映射/過濾等操作,且仍返回延遲對象,同樣具有延遲加載效果,方便開發使用。
除了上述實現方案之外,延遲加載也可通過其它類似方式實現,例如下述代碼,通過運行時改變內部Supplier變量的對象類型來判斷是否已加載對象并返回,并采用synchronized同步方法來實現線程安全。
還有一種實現更為簡便,代碼如下所示,利用Java中ConcurrentHashMap本身的同步機制來實現線程安全。
參考
- 關于延遲加載的一些應用
總結
- 上一篇: Visual Studio 类向导HRE
- 下一篇: NES模拟器开发笔记(001)缘起、资料