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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

自己构建一个高效缓存服务!

發布時間:2025/3/20 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自己构建一个高效缓存服务! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

??點擊上方?好好學java?,選擇?星標?公眾號

重磅資訊、干貨,第一時間送達 今日推薦:分享一個牛逼的 Java 開源后臺管理系統,不要造輪子了!個人原創+1博客:點擊前往,查看更多 作者:flydean 原文:https://segmentfault.com/a/1190000022237396

在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

總結

以上是生活随笔為你收集整理的自己构建一个高效缓存服务!的全部內容,希望文章能夠幫你解決所遇到的問題。

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