1. 程式人生 > >高併發學習養成計劃4

高併發學習養成計劃4

構建高效且可伸縮的結果快取

簡單的快取可能將效能瓶頸轉變為可伸縮性瓶頸,即使快取適用於提升單執行緒的效能

 

使用HashMap和同步機制來快取初始化資料

 

Memoizer1是使用synchronized和HashMap構建的快取,雖然將計算結果是儲存在了map中,但是由於將所有執行緒給序列化了,所以併發量極差,程式的伸縮性及其不友好。甚至可能在某些情況下還不如不加快取的效能高。所以需要將該種實現方法進行改進。

 

使用ConcurrentHashMap實現快取

 

通過Memoizer2可以分析出來,此種實現方式雖然改善了系統的伸縮性,但是在沒有鎖的情況下可能造成同一個算式多次計算(因為沒有加鎖,所以當執行緒1訪問compute時,判斷result為空,此時跳到執行緒2訪問compute時,拿與執行緒1同一個資料,此時會判斷為空,執行緒2又將進行計算)。這對於快取來說完全是沒有意義的。

 

 

 

 

利用FutureTask實現快取

通過Memoizer3可以發現,該種實現的快取已經近似完美。因為相比Memoizer1該種實現方式沒有加鎖,增加了系統的彈性。並且在存放前並沒有進行復雜的運算,而是直接以一個假的結果放入cache中,這樣在另一個執行緒過來的時候,獲取result不為空,則不會重複進行運算,而是在get處等待 第一個運算的執行緒運算完成。與Memoizer2相比,大大減小了重複運算的概率。但是仔細研究下還是有問題的,還會在假的結果沒有放進快取的時候,可能這個時候另一個執行緒來了,那麼將又要進行重複運算了

 

 

Mermoizer的最終實現

Mermoizer3的問題已經知道了,它的問題是出在若沒有則新增不是原子操作,最後導致問題的出現。Mermoizer採用了concurrentHashMap中的同步方法錯沒有則新增,從而保證了這個過程的原子性。可能還有人會問為什麼要加while迴圈。那是因為快取的是future不是值,會導致快取汙染的問題(也就是當某個假值被存入快取中,真實的值還在通過執行緒計算,那麼當沒有計算完這個結果而終止了這個執行緒,那麼將在這個快取中產生了垃圾值。那麼以後取得值都是垃圾值,所以是及其不安全得,故我們要捕獲CancellationException,當發現終止任務異常之後,應該立即將快取中得假值清空)。這麼清空得話,另一些在等待拿到值的執行緒,就不會拿到值。而我們要做的就是使這些在等待但沒有拿到值得執行緒在迴圈一次操作,讓新的執行緒去計算。當計算無誤後,所有拿某個快取值得執行緒都結束執行。