亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

Java構(gòu)建高效結(jié)果緩存方法示例

瀏覽:109日期:2022-09-02 13:16:37

緩存是現(xiàn)代應(yīng)用服務(wù)器中非常常用的組件。除了第三方緩存以外,我們通常也需要在java中構(gòu)建內(nèi)部使用的緩存。那么怎么才能構(gòu)建一個(gè)高效的緩存呢? 本文將會(huì)一步步的進(jìn)行揭秘。

使用HashMap

緩存通常的用法就是構(gòu)建一個(gè)內(nèi)存中使用的Map,在做一個(gè)長(zhǎng)時(shí)間的操作比如計(jì)算之前,先在Map中查詢一下計(jì)算的結(jié)果是否存在,如果不存在的話再執(zhí)行計(jì)算操作。

我們定義了一個(gè)代表計(jì)算的接口:

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

該接口定義了一個(gè)calculate方法,接收一個(gè)參數(shù),并且返回計(jì)算的結(jié)果。

我們要定義的緩存就是這個(gè)Calculator具體實(shí)現(xiàn)的一個(gè)封裝。

我們看下用HashMap怎么實(shí)現(xiàn):

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; } @Override public 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,在調(diào)用calculate方法中,實(shí)際上調(diào)用了封裝的Calculator的calculate方法。

因?yàn)镠ashMap不是線程安全的,所以這里我們使用了synchronized關(guān)鍵字,從而保證一次只有一個(gè)線程能夠訪問(wèn)calculate方法。

雖然這樣的設(shè)計(jì)能夠保證程序的正確執(zhí)行,但是每次只允許一個(gè)線程執(zhí)行calculate操作,其他調(diào)用calculate方法的線程將會(huì)被阻塞,在多線程的執(zhí)行環(huán)境中這會(huì)嚴(yán)重影響速度。從而導(dǎo)致使用緩存可能比不使用緩存需要的時(shí)間更長(zhǎng)。

使用ConcurrentHashMap

因?yàn)镠ashMap不是線程安全的,那么我們可以嘗試使用線程安全的ConcurrentHashMap來(lái)替代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; } @Override public V calculate(A arg) throws InterruptedException { V result= cache.get(arg); if( result ==null ){ result= calculator.calculate(arg); cache.put(arg, result); } return result; }}

上面的例子中雖然解決了之前的線程等待的問(wèn)題,但是當(dāng)有兩個(gè)線程同時(shí)在進(jìn)行同一個(gè)計(jì)算的時(shí)候,仍然不能保證緩存重用,這時(shí)候兩個(gè)線程都會(huì)分別調(diào)用計(jì)算方法,從而導(dǎo)致重復(fù)計(jì)算。

我們希望的是如果一個(gè)線程正在做計(jì)算,其他的線程只需要等待這個(gè)線程的執(zhí)行結(jié)果即可。很自然的,我們想到了之前講到的FutureTask。FutureTask表示一個(gè)計(jì)算過(guò)程,我們可以通過(guò)調(diào)用FutureTask的get方法來(lái)獲取執(zhí)行的結(jié)果,如果該執(zhí)行正在進(jìn)行中,則會(huì)等待。

下面我們使用FutureTask來(lái)進(jìn)行改寫(xiě)。

FutureTask

@Slf4jpublic 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; } @Override public 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來(lái)封裝計(jì)算,并且將FutureTask作為Map的value。

上面的例子已經(jīng)體現(xiàn)了很好的并發(fā)性能。但是因?yàn)閕f語(yǔ)句是非原子性的,所以對(duì)這一種先檢查后執(zhí)行的操作,仍然可能存在同一時(shí)間調(diào)用的情況。

這個(gè)時(shí)候,我們可以借助于ConcurrentHashMap的原子性操作putIfAbsent來(lái)重寫(xiě)上面的類(lèi):

@Slf4jpublic 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; } @Override public 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>() { @Override public 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; } } }}

上面使用了一個(gè)while循環(huán),來(lái)判斷從cache中獲取的值是否存在,如果不存在則調(diào)用計(jì)算方法。

上面我們還要考慮一個(gè)緩存污染的問(wèn)題,因?yàn)槲覀冃薷牧司彺娴慕Y(jié)果,如果在計(jì)算的時(shí)候,計(jì)算被取消或者失敗,我們需要從緩存中將FutureTask移除。

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

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持好吧啦網(wǎng)。

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 亚洲综合色丁香婷婷六月图片 | 精品无人区乱码一区二区三区手机 | 欧美激情一区二区三区蜜桃视频 | 五月天丁香花婷婷视频网 | 一级做a免费视频 | 91嫩草视频在线观看 | 婬荡少妇21p | 国产一区二区三区波多野吉衣 | 欧美激情视频一级视频一级毛片 | 日比视频在线观看 | 黄片1234| 久久中文字幕亚洲 | 麻豆传媒官方入口 | 欧美特级特黄a大片免费 | 久久久欧美综合久久久久 | 日韩毛片在线 | 亚洲一区二区三 | 三级网战| 啪一啪日一日 | 亚洲一级理论片 | 国模偷拍在线观看免费视频 | 韩国一级特黄清高免费大片 | 久久夜色精品国产尤物 | 久久er国产精品免费观看2 | 国产成人免费在线视频 | 亚洲欧美日韩国产精品26u | 女人被狂躁的免费视频网站软件 | 欧美一级三级在线观看 | 国产成人免费永久播放视频平台 | 国产精品吹潮在线播放 | 国产一级做a爰片久久毛片99 | 一级成人a免费视频 | 狠狠干婷婷 | 国产九九热视频 | 黄a一级 | 成人精品视频一区二区三区尤物 | 国产美女视频爽爽爽 | 精品国产一区二区三区久久影院 | 黄色网址视频免费 | 国产尤物在线播放 | 国产视频在线一区 |