日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

在java中构建高效的结果缓存

發布時間:2024/2/28 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在java中构建高效的结果缓存 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 使用HashMap
    • 使用ConcurrentHashMap
    • FutureTask

在java中構建高效的結果緩存

緩存是現代應用服務器中非常常用的組件。除了第三方緩存以外,我們通常也需要在java中構建內部使用的緩存。那么怎么才能構建一個高效的緩存呢? 本文將會一步步的進行揭秘。

使用HashMap

緩存通常的用法就是構建一個內存中使用的Map,在做一個長時間的操作比如計算之前,先在Map中查詢一下計算的結果是否存在,如果不存在的話再執行計算操作。

我們定義了一個代表計算的接口:

public interface Calculator<A, V> {V calculate(A arg) throws InterruptedException; }

該接口定義了一個calculate方法,接收一個參數,并且返回計算的結果。

我們要定義的緩存就是這個Calculator具體實現的一個封裝。

我們看下用HashMap怎么實現:

public class MemoizedCalculator1<A, V> implements Calculator<A, V> {private final Map<A, V> cache= new HashMap<A, V>();private final Calculator<A, V> calculator;public MemoizedCalculator1(Calculator<A, V> calculator){this.calculator=calculator;}@Overridepublic synchronized V calculate(A arg) throws InterruptedException {V result= cache.get(arg);if( result ==null ){result= calculator.calculate(arg);cache.put(arg, result);}return result;} }

MemoizedCalculator1封裝了Calculator,在調用calculate方法中,實際上調用了封裝的Calculator的calculate方法。

因為HashMap不是線程安全的,所以這里我們使用了synchronized關鍵字,從而保證一次只有一個線程能夠訪問calculate方法。

雖然這樣的設計能夠保證程序的正確執行,但是每次只允許一個線程執行calculate操作,其他調用calculate方法的線程將會被阻塞,在多線程的執行環境中這會嚴重影響速度。從而導致使用緩存可能比不使用緩存需要的時間更長。

使用ConcurrentHashMap

因為HashMap不是線程安全的,那么我們可以嘗試使用線程安全的ConcurrentHashMap來替代HashMap。如下所示:

public class MemoizedCalculator2<A, V> implements Calculator<A, V> {private final Map<A, V> cache= new ConcurrentHashMap<>();private final Calculator<A, V> calculator;public MemoizedCalculator2(Calculator<A, V> calculator){this.calculator=calculator;}@Overridepublic V calculate(A arg) throws InterruptedException {V result= cache.get(arg);if( result ==null ){result= calculator.calculate(arg);cache.put(arg, result);}return result;} }

上面的例子中雖然解決了之前的線程等待的問題,但是當有兩個線程同時在進行同一個計算的時候,仍然不能保證緩存重用,這時候兩個線程都會分別調用計算方法,從而導致重復計算。

我們希望的是如果一個線程正在做計算,其他的線程只需要等待這個線程的執行結果即可。很自然的,我們想到了之前講到的FutureTask。FutureTask表示一個計算過程,我們可以通過調用FutureTask的get方法來獲取執行的結果,如果該執行正在進行中,則會等待。

下面我們使用FutureTask來進行改寫。

FutureTask

@Slf4j public class MemoizedCalculator3<A, V> implements Calculator<A, V> {private final Map<A, Future<V>> cache= new ConcurrentHashMap<>();private final Calculator<A, V> calculator;public MemoizedCalculator3(Calculator<A, V> calculator){this.calculator=calculator;}@Overridepublic V calculate(A arg) throws InterruptedException {Future<V> future= cache.get(arg);V result=null;if( future ==null ){Callable<V> callable= new Callable<V>() {@Overridepublic V call() throws Exception {return calculator.calculate(arg);}};FutureTask<V> futureTask= new FutureTask<>(callable);future= futureTask;cache.put(arg, futureTask);futureTask.run();}try {result= future.get();} catch (ExecutionException e) {log.error(e.getMessage(),e);}return result;} }

上面的例子,我們用FutureTask來封裝計算,并且將FutureTask作為Map的value。

上面的例子已經體現了很好的并發性能。但是因為if語句是非原子性的,所以對這一種先檢查后執行的操作,仍然可能存在同一時間調用的情況。

這個時候,我們可以借助于ConcurrentHashMap的原子性操作putIfAbsent來重寫上面的類:

@Slf4j public class MemoizedCalculator4<A, V> implements Calculator<A, V> {private final Map<A, Future<V>> cache= new ConcurrentHashMap<>();private final Calculator<A, V> calculator;public MemoizedCalculator4(Calculator<A, V> calculator){this.calculator=calculator;}@Overridepublic V calculate(A arg) throws InterruptedException {while (true) {Future<V> future = cache.get(arg);V result = null;if (future == null) {Callable<V> callable = new Callable<V>() {@Overridepublic V call() throws Exception {return calculator.calculate(arg);}};FutureTask<V> futureTask = new FutureTask<>(callable);future = cache.putIfAbsent(arg, futureTask);if (future == null) {future = futureTask;futureTask.run();}try {result = future.get();} catch (CancellationException e) {log.error(e.getMessage(), e);cache.remove(arg, future);} catch (ExecutionException e) {log.error(e.getMessage(), e);}return result;}}} }

上面使用了一個while循環,來判斷從cache中獲取的值是否存在,如果不存在則調用計算方法。

上面我們還要考慮一個緩存污染的問題,因為我們修改了緩存的結果,如果在計算的時候,計算被取消或者失敗,我們需要從緩存中將FutureTask移除。

本文的例子可以參考https://github.com/ddean2009/learn-java-concurrency/tree/master/MemoizedCalculate

更多精彩內容且看:

  • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

更多內容請訪問 flydean的博客

總結

以上是生活随笔為你收集整理的在java中构建高效的结果缓存的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。