《玩轉(zhuǎn)Java并發(fā)工具、精通JUC、成為并發(fā)多面手》構(gòu)建高性能緩存
引言
《玩轉(zhuǎn)Java并發(fā)工具、精通JUC、成為并發(fā)多面手》構(gòu)建高性能緩存這部分的個(gè)人筆記。本節(jié)為單純的實(shí)戰(zhàn),主要是把之前學(xué)習(xí)并發(fā)編程的知識(shí)點(diǎn)串起來。
挺有意思的一個(gè)demo,可以快速了解到一些并發(fā)編程的時(shí)候需要注意的一些問題。
目錄
整個(gè)高性能構(gòu)建的梳理思路如下:
使用最簡單的HashMap
高并發(fā)訪問重復(fù)計(jì)算性能問題
復(fù)用性能較差的問題
分析HashMap實(shí)現(xiàn)的問題
解決復(fù)用性能較差的問題
裝飾模式抽象計(jì)算業(yè)務(wù)
防止業(yè)務(wù)重復(fù)計(jì)算
如何處理高并發(fā)訪問重復(fù)計(jì)算性能問題
使用Future改寫計(jì)算實(shí)現(xiàn)接口
增加泛型
防止同一個(gè)時(shí)刻大量緩存過期增加系統(tǒng)壓力
防止緩存污染
為什么需要緩存過期?
為什么要增加隨機(jī)性?
緩存過期和增加隨機(jī)性
整體測(cè)試
代碼
一個(gè)簡單的小demo,可以直接拷貝下面的包所屬的各個(gè)版本代碼到自己的項(xiàng)目閱讀即可:https://gitee.com/lazyTimes/interview/tree/master/src/main/java/com/zxd/interview/mycache
一、構(gòu)建步驟
1. 使用最簡單的HashMap
最基礎(chǔ)的版本實(shí)現(xiàn)非常簡單,這是我們通常會(huì)想到的應(yīng)用緩存實(shí)現(xiàn)方案,這里使用了Lombok的@Slf4j
注解進(jìn)行日志打印。
整個(gè)邏輯非常簡單,首先通過計(jì)算方法匹配緩存,如果有就取緩存內(nèi)容,否則就調(diào)用計(jì)算方法然后把結(jié)果緩存到HashMap當(dāng)中。
/**??
?*?初版高速緩存實(shí)現(xiàn)??
?*?1.?使用簡單的HashMap??
?*?2.?在高速緩存中進(jìn)行計(jì)算??
?*??
?*?暴露問題:??
?*?1.?復(fù)用性能查??
?*?2.?HashMap線程不安全,有可能重復(fù)判斷??
?*/??
4j??
public?class?MyCacheVersion1?{??
??
????private?final?Map<String,?Integer>?cache?=?new?HashMap<>();??
??
????/**??
?????*?根據(jù)參數(shù)計(jì)算結(jié)果,此參數(shù)對(duì)于同一個(gè)參數(shù)會(huì)計(jì)算同樣的結(jié)果??
?????*?1.?如果緩存存在結(jié)果,直接返回??
?????*?2.?如果緩存不存在,則需要計(jì)算添加到map之后才返回??
?????*?@param?userId??
?????*?@return??
?????*/??
????public?Integer?compute(String?userId)?throws?InterruptedException?{??
????????if(cache.containsKey(userId)){??
????????????log.info("cached?=>?{}",?userId);??
????????????return?cache.get(userId);??
????????}??
????????log.info("doCompute?=>?{}",?userId);??
????????Integer?result?=?doCompute(userId);??
????????//不存在緩存就加入??
????????cache.put(userId,?result);??
????????return?result;??
????}??
??
????private?Integer?doCompute(String?userId)?throws?InterruptedException?{??
????????TimeUnit.SECONDS.sleep(5);??
????????return?Integer.parseInt(userId);??
????}??
??
??
}
初版存在較多的問題,比較顯著的問題是compute
這個(gè)方法在多線程的環(huán)境是不安全的,我們可以編寫測(cè)試程序驗(yàn)證。
在測(cè)試程序中,我們使用線程池構(gòu)建100個(gè)線程處理1000個(gè)計(jì)算任務(wù)。從打印結(jié)果中我們隨機(jī)抽取其中一個(gè)數(shù)字很容易出現(xiàn)計(jì)算兩次的情況,比如下面的情況:
??
/**??
?*?對(duì)應(yīng)當(dāng)前版本的測(cè)試程序??
?*?1.?HashMap線程不安全??
?*?2.?此程序驗(yàn)證線程安全問題??
?*/??
4j??
public?class?Test?{??
??
????public?static?void?main(String[]?args)?throws?InterruptedException?{??
????????ExecutorService?executorService?=?Executors.newFixedThreadPool(100);??
????????MyCacheVersion1?objectObjectMyCacheVersion1?=?new?MyCacheVersion1();??
????????Random?random?=?new?Random(100);??
????????for?(int?i?=?0;?i?<?1000;?i++)?{??
????????????executorService.execute(()?->?{??
????????????????int?randomInt?=?random.nextInt(100);??
????????????????try?{??
????????????????????Integer?user?=?objectObjectMyCacheVersion1.compute(String.valueOf(randomInt));??
????????????????}?catch?(InterruptedException?e)?{??
????????????????????throw?new?RuntimeException(e);??
????????????????}??
??
????????????});??
????????}??
????????executorService.shutdown();??
????}/**運(yùn)行結(jié)果
?????測(cè)試結(jié)果:可以看到高并發(fā)的情況下非常有可能出現(xiàn)重復(fù)計(jì)算然后cache的情況??
?????10:56:41.437?[pool-1-thread-53]?INFO??c.z.i.m.version1.MyCacheVersion1?-?doCompute?=>?59??
?????10:56:41.447?[pool-1-thread-97]?INFO??c.z.i.m.version1.MyCacheVersion1?-?doCompute?=>?59?????
?????*/
}
可以發(fā)現(xiàn)doCompute => 59
算了多次。
處理線程不安全問題
線程不安全問題最簡單的處理方式就是方法串行:
public?synchronized?Integer?compute(String?userId)?throws?InterruptedException
如果加入synchronized
,則整個(gè)線程的處理會(huì)串行執(zhí)行,但是效率極低。
/**運(yùn)行結(jié)果??
?串行之后執(zhí)行效率極低,基本無法使用?
?11:14:15.851?[pool-1-thread-1]?INFO??c.z.i.m.version1.MyCacheVersion1?-?doCompute?=>?15??
?11:14:20.862?[pool-1-thread-100]?INFO??c.z.i.m.version1.MyCacheVersion1?-?doCompute?=>?52?11:14:25.874?[pool-1-thread-99]?INFO??c.z.i.m.version1.MyCacheVersion1?-?doCompute?=>?55?
?*/
2. 分析HashMap實(shí)現(xiàn)的問題
簡單分析HashMap的緩存實(shí)現(xiàn),主要的問題如下:
高并發(fā)訪問重復(fù)計(jì)算性能問題
復(fù)用性能較差的問題
3. 裝飾模式抽象計(jì)算業(yè)務(wù)
我們先解決復(fù)用性能較差的問題,,這里需要使用裝飾模式進(jìn)行改寫。首先定義ComputeAble<A, V>
接口,這個(gè)接口定義了抽象計(jì)算行為。
/**??
?*?可計(jì)算接口??
?*?裝飾接口??
?*/??
public?interface?ComputeAble<A,?V>{??
??
????/**??
?????*?根據(jù)指定參數(shù)?A?進(jìn)行計(jì)算,計(jì)算結(jié)果為?V??
?????*?@description?根據(jù)指定參數(shù)?A?進(jìn)行計(jì)算,計(jì)算結(jié)果為?V??
?????*?@param?arg?泛型參數(shù)??
?????*?@return?V?返回計(jì)算后結(jié)果??
?????*?@author?xander??
?????*?@date?2023/6/15?15:42??
?????*/????
?????V?doCompute(A?arg)?throws?Exception;??
}
定義一個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)計(jì)算接口。
/**??
?*?裝飾模式改寫接口??
?*/??
public?class?ExpensiveCompute?implements?ComputeAble<String,?Integer>{??
???? ??
????public?Integer?doCompute(String?arg)?throws?InterruptedException?{??
????????TimeUnit.SECONDS.sleep(5);
????????return?Integer.parseInt(arg);??
????}??
}
第二個(gè)版本的緩存實(shí)現(xiàn)區(qū)別是使用了裝飾模式封裝計(jì)算方法,其他方法暫時(shí)不做調(diào)整。
/**??
?*?第二版,使用裝飾模式進(jìn)行改造??
?*?synchronized?同步加鎖??
?*?@author??
?*?@version?v1.0.0??
?*?@Package?:?version2??
?*?@Description?:?使用裝飾模式進(jìn)行改造??
?*?@Create?on?:?2023/6/15?16:29??
?**/ 4j??
public?class?MyCacheVersion2?{??
??
????/**??
?????*?緩存??
?????*/??
????private?final?Map<String,?Integer>?cache?=?new?HashMap<>();??
????/**??
?????*?計(jì)算方法實(shí)現(xiàn)對(duì)象??
?????*/??
????private?final?static?ComputeAble<String,?Integer>?COMPUTE?=?new?ExpensiveCompute();??
??
????public?synchronized?Integer?compute(String?userId)?throws?Exception?{??
????????if(cache.containsKey(userId)){??
????????????log.info("cached?=>?{}",?userId);??
????????????return?cache.get(userId);??
????????}??
????????log.info("doCompute?=>?{}",?userId);??
????????Integer?result?=?doCompute(userId);??
????????//?不存在緩存就加入??
????????cache.put(userId,?result);??
????????return?result;??
????}??
??
????/**??
?????*?計(jì)算方法由具體的類實(shí)現(xiàn)封裝??
?????*?@param?userId??
?????*?@return??
?????*?@throws?InterruptedException??
?????*/
?????private?Integer?doCompute(String?userId)?throws?Exception?{??
????????return?COMPUTE.doCompute(userId);??
????}??
}
通過上面的代碼了解到,MyCacheVersion2 緩存實(shí)現(xiàn)類的具體計(jì)算邏輯抽象到具體的實(shí)現(xiàn)類當(dāng)中,如果想要切換新的邏輯,可以改寫COMPUTE
的實(shí)現(xiàn)類。
4. 使用Future改寫計(jì)算實(shí)現(xiàn)接口處理重復(fù)計(jì)算問題
我們簡單分析HashMap的緩存實(shí)現(xiàn),主要問題如下:
高并發(fā)訪問重復(fù)計(jì)算性能問題
復(fù)用性能較差的問題,通過裝飾模式改寫。
復(fù)用較差的問題處理了,下面介紹高并發(fā)訪問重復(fù)計(jì)算問題處理辦法,我們一步步介紹改寫過程。為了方便理解這里暫時(shí)先用具體類型代替泛型。
首先把Map的value存儲(chǔ)改為存儲(chǔ)Future< V > 結(jié)果,并且把HashMap改為線程安全的ConcurrentHashMap。
private?final?Map<String,?Future<Integer>>?concurrentHashMap?=?new?ConcurrentHashMap<>();
構(gòu)建 FutureTask 對(duì)象,這里使用 Lambda 表達(dá)式直接調(diào)用 doCompute 方法。
FutureTask<Integer>?future?=?new?FutureTask<>(()?->?doCompute(userId));
在計(jì)算函數(shù)中,大致的邏輯并沒有改變,但是需要注意下面的細(xì)節(jié):
future.get() 在獲取到結(jié)果之前會(huì)進(jìn)行阻塞。
ConcurrentHashMap 在類似如果不存在就加入的復(fù)合操作情況下需要考慮重復(fù)設(shè)置緩存的問題。
putIfAbsent 可以做一些復(fù)合操作,如果設(shè)置緩存失敗,說明有其他線程做過同樣的操作,此時(shí)就可以重新操作一次獲取結(jié)果即可。
putIfAbsent不成功為什么不直接獲取結(jié)果,而是要再計(jì)算一次,這是為了防止緩存剛好獲取獲取到一個(gè)null的值。
??
/**??
?*?第三個(gè)版本??
?*?1.?優(yōu)化多線程訪問重復(fù)計(jì)算問題??
?*??
?*?*?@author??
?*?@version?v1.0.0??
?*?@Package?:?version3??
?*?@Description?:?第三個(gè)版本??
?*?@Create?on?:?2023/6/15?16:47??
?**/ 4j??
public?class?MyCacheVersion3?{??
????/**??
?????*?改造,并發(fā)不安全集合改為并發(fā)安全集合??
?????*?value?存儲(chǔ)為?future?的值??
?????*/??
????private?final?Map<String,?Future<Integer>>?concurrentHashMap?=?new?ConcurrentHashMap<>();??
??
????/**??
?????*?計(jì)算實(shí)現(xiàn)類??
?????*/??
????private?static?final?ComputeAble<String,?Integer>?COMPUTEABLE?=?new?ExpensiveCompute();??
??
????/**??
?????*?先使用具體類型實(shí)現(xiàn),后續(xù)改為使用泛型實(shí)現(xiàn)??
?????*?1.?使用?FutureTask?對(duì)于要計(jì)算的值進(jìn)行封裝,根據(jù)?FutureTask?特性,獲取到結(jié)果之前單個(gè)線程會(huì)一直等待??
?????*?2.?由于計(jì)算方法變動(dòng),所有的代碼需要調(diào)整??
?????*?3.?concurrentHashMap.get()?在?if?判斷的時(shí)候依然存在非原子行為,所以在設(shè)置的時(shí)候使用?putIfAbsent?原子操作??
?????*??
?????*?@param?userId??
?????*?@return??
?????*?@throws?InterruptedException??
?????*?@throws?ExecutionException??
?????*/????public?Integer?compute(String?userId)?throws?InterruptedException,?ExecutionException?{??
????????Future<Integer>?result?=?concurrentHashMap.get(userId);??
????????//?如果獲取不到內(nèi)容,說明不在緩存當(dāng)中??
????????if(Objects.isNull(result)){??
????????????//?此時(shí)利用callAble?線程任務(wù)指定任務(wù)獲取,在獲取到結(jié)果之前線程會(huì)阻塞??
????????????FutureTask<Integer>?future?=?new?FutureTask<>(()?->?doCompute(userId));??
????????????//把新的future覆蓋之前獲取的future??
????????????result?=?future;??
????????????//?執(zhí)行??
????????????future.run();??
????????????log.info("FutureTask?調(diào)用計(jì)算函數(shù)");??
????????????result?=?concurrentHashMap.putIfAbsent(userId,?result);??
????????????//?如果返回null,說明這個(gè)記錄被添加過了??
????????????if(Objects.isNull(result)){??
????????????????log.info("其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算");??
????????????????//?說明其他線程已經(jīng)設(shè)置過值,這里重新跑一次計(jì)算方法即可直接獲取??
????????????????result?=?future;??
????????????????//?再重新跑一次??
????????????????future.run();??
????????????????return?result.get();??
????????????}else{??
????????????????return?result.get();??
????????????}??
????????}??
????????return?result.get();??
????}??
??
????/**??
?????*?計(jì)算方法由具體的類實(shí)現(xiàn)封裝??
?????*?@param?userId??
?????*?@return??
?????*?@throws?InterruptedException??
?????*/????private?Integer?doCompute(String?userId)?throws?Exception?{??
????????return?COMPUTEABLE.doCompute(userId);??
????}??
}
5. 泛型接口改寫
有了上面的實(shí)現(xiàn)基礎(chǔ),改為為泛型就容易很多了,泛型的寫法實(shí)際上就是把之前的具體類型轉(zhuǎn)為泛型即可。
這里的代碼可能不完整,建議把開頭部分的倉庫代碼放到本地驗(yàn)證。
/**??
?*?第四個(gè)版本,方法改為泛型實(shí)現(xiàn)??
?*?@author??
?*?@version?v1.0.0??
?*?@Package?:?version3??
?*?@Description?:?第三個(gè)版本??
?*?@Create?on?:?2023/6/15?16:47??
?**/ 4j??
public?class?MyCacheVersion4<A,?V>?{??
????/**??
?????*?改造,并發(fā)不安全集合改為并發(fā)安全集合??
?????*?value?存儲(chǔ)為?future的值??
?????*/??
????private?final?Map<A,?Future<V>>?concurrentHashMap?=?new?ConcurrentHashMap<>();??
??
????private?final?ComputeAble?computeAble?=?new?ExpensiveCompute<>();??
??
????/**??
?????*?先使用具體類型實(shí)現(xiàn),后續(xù)改為使用泛型實(shí)現(xiàn)??
?????*?1.?使用?FutureTask?對(duì)于要計(jì)算的值進(jìn)行封裝,根據(jù)?FutureTask?特性,獲取到結(jié)果之前單個(gè)線程會(huì)一直等待??
?????*?2.?由于計(jì)算方法變動(dòng),所有的代碼需要調(diào)整??
?????*?3.?concurrentHashMap.get()?在?if?判斷的時(shí)候依然存在非原子行為,所以在設(shè)置的時(shí)候使用?putIfAbsent?原子操作??
?????*?4.?重構(gòu),使用泛型參數(shù)??
?????*?@param?arg??
?????*?@return??
?????*?@throws?InterruptedException??
?????*?@throws?ExecutionException??
?????*/
?????public?V?compute(A?arg)?throws?InterruptedException,?ExecutionException?{??
????????Future<V>?result?=?concurrentHashMap.get(arg);??
????????//?如果獲取不到內(nèi)容,說明不在緩存當(dāng)中??
????????if(Objects.isNull(result)){??
????????????//?此時(shí)利用callAble?線程任務(wù)指定任務(wù)獲取,在獲取到結(jié)果之前線程會(huì)阻塞??
????????????FutureTask<V>?future?=?new?FutureTask<>(new?Callable<V>()?{??
???????????????? ??
???????????????? ("unchecked")??
????????????????public?V?call()?throws?Exception?{??
????????????????????return?(V)?computeAble.doCompute(arg);??
????????????????}??
????????????});??
????????????//把新的future覆蓋之前獲取的future??
????????????result?=?future;??
????????????//?執(zhí)行??
????????????future.run();??
????????????log.info("FutureTask?調(diào)用計(jì)算函數(shù)");??
????????????result?=?concurrentHashMap.putIfAbsent(arg,?result);??
????????????//?如果返回null,說明這個(gè)記錄被添加過了??
????????????if(Objects.isNull(result)){??
????????????????log.info("其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算");??
????????????????//?說明其他線程已經(jīng)設(shè)置過值,這里重新跑一次計(jì)算方法即可直接獲取??
????????????????result?=?future;??
????????????????//?再重新跑一次??
????????????????future.run();??
????????????????return?result.get();??
????????????}else{??
????????????????return?result.get();??
????????????}??
????????}??
????????return?result.get();??
????}??
}
6. 可能失敗的計(jì)算導(dǎo)致緩存污染問題處理
觀察另一個(gè)計(jì)算實(shí)現(xiàn),當(dāng)我們的使用下面的方式會(huì)有什么樣的效果?
/**??
?*??可能會(huì)出現(xiàn)失敗的計(jì)算方法??
?*?@author?Xander??
?*?@version?v1.0.0??
?*?@Package?:?compute??
?*?@Description?:?可能會(huì)出現(xiàn)失敗的計(jì)算方法??
?*?@Create?on?:?2023/6/19?10:40??
?**/public?class?MayFailCompute?implements?ComputeAble<String,?Integer>{??
??
????/**??
?????*?觸發(fā)失敗閾值??
?????*/??
????private?static?final?int?FAILURE_THRESHOLD?=?50;??
??
????/**??
?????*?隨機(jī)數(shù)生成器??
?????*/??
????private?static?final?Random?RANDOM?=?new?Random(100);??
??
????/**??
?????*?有可能會(huì)出現(xiàn)失敗的計(jì)算方法??
?????*?@description?有可能會(huì)出現(xiàn)失敗的計(jì)算方法??
?????*?@param?arg??
?????*?@return?java.lang.Integer??
?????*?@author?xander??
?????*?@date?2023/6/19?10:41??
?????*/???? ??
????public?Integer?doCompute(String?arg)?throws?Exception?{??
????????if(RANDOM.nextInt()?<?FAILURE_THRESHOLD){??
????????????throw?new?Exception("自定義異常");??
????????}??
????????TimeUnit.MILLISECONDS.sleep(5);??
????????return?Integer.parseInt(arg);??
????}??
}
由于一開始我們就使用裝飾模式改寫過代碼,所以要替換實(shí)現(xiàn)類非常簡單:
private?final?ComputeAble?computeAble?=?new?MayFailCompute();
測(cè)試的結(jié)果毫不意外的出現(xiàn)大量的失敗。這樣結(jié)果不符合預(yù)期,雖然 50%的失敗率相當(dāng)高,但實(shí)際上更多的是從緩存中獲取的結(jié)果就是異常信息,這種情況就是緩存污染問題。
為了解決緩存污染問題,我們需要在try/catch
中對(duì)于不同的情況進(jìn)行不同的處理。在之前計(jì)算處理邏輯中一共會(huì)出現(xiàn)下面三種情況:
CancellationException:線程被取消拋出的異常。
InterruptedException:線程被中斷時(shí)候拋出的異常。
ExecutionException:試圖檢索一個(gè)因拋出異常而中止的任務(wù)的結(jié)果時(shí)拋出的異常。
對(duì)于不同的異常要對(duì)應(yīng)不同的處理態(tài)度:
CancellationException 和 InterruptedException 基本都是人為操作,這時(shí)候應(yīng)該立即終止任務(wù)。
根據(jù)方法邏輯我們知道方法是有可能計(jì)算成功的,只不過需要多重試幾次。
while(true) 的加入可以讓出錯(cuò)之后自動(dòng)重新進(jìn)行計(jì)算直到成功為止,但是如果是人為取消,就需要拋出異常并且手動(dòng)結(jié)束任務(wù)。
我們把上面的處理思路轉(zhuǎn)化為代碼,相關(guān)注釋已經(jīng)加入,可以看下面的結(jié)果:
??
/**??
?*?<pre>??
?*?第五個(gè)版本,當(dāng)碰到會(huì)拋出異常的計(jì)算方法的情況這時(shí)候應(yīng)該重新計(jì)算??
?*?對(duì)于不同的異常,也要對(duì)應(yīng)不同的處理態(tài)度:??
?*??
?*?-?CancellationException?和?InterruptedException?基本都是人為操作,這時(shí)候應(yīng)該立即終止任務(wù)。??
?*?-?根據(jù)方法邏輯,我們可以知道方法是有可能計(jì)算成功的,只不過需要多重試幾次。??
?*?-?while(true)?的加入可以讓出錯(cuò)之后自動(dòng)重新進(jìn)行計(jì)算直到成功為止,但是如果是人為取消,就需要拋出異常并且結(jié)束。??
?*?</pre>??
?*?@author??
?*?@version?v1.0.0??
?*?@Package?:?version3??
?*?@Description?:?第五個(gè)版本??
?*?@Create?on?:?2023/6/15?16:47??
?**/ 4j??
public?class?MyCacheVersion5<A,?V>?{??
????/**??
?????*?改造,并發(fā)不安全集合改為并發(fā)安全集合??
?????*?value?存儲(chǔ)為?future的值??
?????*/??
????private?final?Map<A,?Future<V>>?concurrentHashMap?=?new?ConcurrentHashMap<>();??
??
????private?final?ComputeAble?computeAble?=?new?MayFailCompute();??
??
???public?V?compute(A?arg)?{??
????????return?doCompute(arg);??
????}??
??
????private?V?doCompute(A?arg)?{??
????????//?對(duì)于重復(fù)計(jì)算進(jìn)行處理??
????????while?(true)?{??
????????????Future<V>?result?=?concurrentHashMap.get(arg);??
????????????try?{??
????????????????//?如果獲取不到內(nèi)容,說明不在緩存當(dāng)中??
????????????????if?(Objects.isNull(result))?{??
????????????????????//?此時(shí)利用callAble?線程任務(wù)指定任務(wù)獲取,在獲取到結(jié)果之前線程會(huì)阻塞??
????????????????????FutureTask<V>?future?=?new?FutureTask<>(new?Callable<V>()?{??
???????????????????????? ??
???????????????????????? ("unchecked")??
????????????????????????public?V?call()?throws?Exception?{??
????????????????????????????return?(V)?computeAble.doCompute(arg);??
??
????????????????????????}??
????????????????????});??
????????????????????//把新的future覆蓋之前獲取的future??
????????????????????result?=?future;??
????????????????????//?執(zhí)行??
????????????????????future.run();??
????????????????????System.out.println("FutureTask?調(diào)用計(jì)算函數(shù)");??
????????????????????result?=?concurrentHashMap.putIfAbsent(arg,?result);??
????????????????????//?如果返回null,說明這個(gè)記錄被添加過了??
????????????????????if?(Objects.isNull(result))?{??
????????????????????????System.out.println("其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算");??
????????????????????????//?說明其他線程已經(jīng)設(shè)置過值,這里重新跑一次計(jì)算方法即可直接獲取??
????????????????????????result?=?future;??
????????????????????????//?再重新跑一次??
????????????????????????future.run();??
????????????????????????return?result.get();??
????????????????????}?else?{??
????????????????????????return?result.get();??
????????????????????}??
????????????????}??
????????????????return?result.get();??
????????????}?catch?(CancellationException?cancellationException)?{??
????????????????log.warn("CancellationException?result?=>?{}",?result);??
????????????????//?線程在執(zhí)行過程當(dāng)中有可能被取消??
????????????????//?被取消的時(shí)候不管如何處理,首先需要先從緩存中移除掉污染緩存??
????????????????concurrentHashMap.remove(arg);??
????????????????throw?new?RuntimeException(cancellationException);??
????????????}?catch?(InterruptedException?e)?{??
????????????????log.warn("InterruptedException?result?=>?{}",?result);??
????????????????//?線程被中斷的異常處理??
????????????????concurrentHashMap.remove(arg);??
????????????????throw?new?RuntimeException(e);??
????????????}?catch?(ExecutionException?e)?{??
//????????????log.warn("ExecutionException?result?=>?{}",?result);??
????????????????log.info("移除緩存Key?=>?{},重新計(jì)算",?arg);??
????????????????concurrentHashMap.remove(arg);??
????????????????//?不會(huì)拋出異常,而是重新在下一次循環(huán)中計(jì)算??
//????????????throw?new?RuntimeException(e);??
????????????/*????????????打印結(jié)果如下:??
????????????FutureTask?調(diào)用計(jì)算函數(shù)??
????????????FutureTask?調(diào)用計(jì)算函數(shù)??
????????????result?=>?65????????????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
????????????result?=>?33????????????result?=>?65????????????result?=>?26????????????15:59:56.584?[pool-1-thread-30]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?75,重新計(jì)算??
????????????result?=>?75????????????15:59:56.584?[pool-1-thread-3]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?35,重新計(jì)算??
????????????15:59:56.584?[pool-1-thread-42]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?67,重新計(jì)算??
????????????15:59:56.584?[pool-1-thread-36]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?75,重新計(jì)算??
????????????15:59:56.584?[pool-1-thread-90]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?40,重新計(jì)算??
????????????15:59:56.585?[pool-1-thread-31]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?13,重新計(jì)算??
????????????15:59:56.586?[pool-1-thread-94]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?60,重新計(jì)算??
????????????Disconnected?from?the?target?VM,?address:?'127.0.0.1:11054',?transport:?'socket'??
????????????Process?finished?with?exit?code?0??
????????????*?*/
??}?catch?(Exception?e)?{??
????????????????log.warn("Exception?result?=>?{}",?result);??
????????????????concurrentHashMap.remove(arg);??
????????????????//?無法處理的未知異常,直接拋出運(yùn)行時(shí)異常不做任何處理。??
????????????????throw?new?RuntimeException(e);??
????????????}??
??
????????}??
????}??
}
最后是測(cè)試部分。
??
/**??
?*?可能失敗的計(jì)算導(dǎo)致緩存污染問題處理??
?*?1.?解決緩存污染問題。??
?*?2.?異常情況嘗試一直重復(fù)計(jì)算。??
?*??
?*?@author?Xander??
?*?@version?v1.0.0??
?*?@Package?:?version5??
?*?@Description?:??
?*?@Create?on?:?2023/6/19?10:49??
?**/public?class?Test?{??
??
????public?static?void?main(String[]?args)?throws?InterruptedException?{??
????????ExecutorService?executorService?=?Executors.newFixedThreadPool(100);??
????????MyCacheVersion5<String,?Integer>?myCacheVersion5?=?new?MyCacheVersion5<>();??
????????Random?random?=?new?Random(100);??
????????for?(int?i?=?0;?i?<?1000;?i++)?{??
????????????executorService.submit(()?->?{??
????????????????int?randomInt?=?random.nextInt(100);??
??
????????????????Integer?user?=?myCacheVersion5.compute(String.valueOf(randomInt));??
????????????????System.out.println("result?=>?"?+?user);??
??
??
????????????});??
????????}??
????????executorService.shutdown();??
????}/**??
?????運(yùn)行結(jié)果:??
?????短期內(nèi)會(huì)有海量異常,這不符合預(yù)期情況。根本原因是緩存不存在過期時(shí)間,會(huì)存在無效的內(nèi)容緩存計(jì)算??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????java.util.concurrent.ExecutionException:?java.lang.Exception:?自定義異常??
?????at?java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)??
?????at?java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)?????at?interview/version5.MyCacheVersion5.compute(MyCacheVersion5.java:63)?????at?interview/version5.Test.lambda$main$0(Test.java:30)?????at?java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)?????at?java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)?????at?java.base/java.util.concurrent.FutureTask.run(FutureTask.java)?????at?java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)?????at?java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)?????at?java.base/java.lang.Thread.run(Thread.java:829)?????Caused?by:?java.lang.Exception:?自定義異常??
?????at?interview/compute.MayFailCompute.doCompute(MayFailCompute.java:37)??
?????at?interview/compute.MayFailCompute.doCompute(MayFailCompute.java:14)?????at?interview/version5.MyCacheVersion5$1.call(MyCacheVersion5.java:47)?????at?java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)?????at?java.base/java.util.concurrent.FutureTask.run(FutureTask.java)?????at?interview/version5.MyCacheVersion5.compute(MyCacheVersion5.java:53)?????...?7?more?????java.util.concurrent.ExecutionException:?java.lang.Exception:?自定義異常??
?????at?java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)??
?????at?java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)?????at?interview/version5.MyCacheVersion5.compute(MyCacheVersion5.java:63)?????at?interview/version5.Test.lambda$main$0(Test.java:30)?????at?java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)?????at?java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)?????at?java.base/java.util.concurrent.FutureTask.run(FutureTask.java)?????at?java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)?????at?java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)?????at?java.base/java.lang.Thread.run(Thread.java:829)?????Caused?by:?java.lang.Exception:?自定義異常??
?????at?interview/compute.MayFailCompute.doCompute(MayFailCompute.java:37)??
?????at?interview/compute.MayFailCompute.doCompute(MayFailCompute.java:14)?????at?interview/version5.MyCacheVersion5$1.call(MyCacheVersion5.java:47)?????at?java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)?????at?java.base/java.util.concurrent.FutureTask.run(FutureTask.java)?????at?interview/version5.MyCacheVersion5.compute(MyCacheVersion5.java:53)?????...?7?more?????java.util.concurrent.ExecutionException:?java.lang.Exception:?自定義異常??
?????at?java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)??
?????at?java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)?????at?interview/version5.MyCacheVersion5.compute(MyCacheVersion5.java:63)?????at?interview/version5.Test.lambda$main$0(Test.java:30)?????at?java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)?????at?java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)?????at?java.base/java.util.concurrent.FutureTask.run(FutureTask.java)?????at?java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)?????at?java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)?????at?java.base/java.lang.Thread.run(Thread.java:829)?????Caused?by:?java.lang.Exception:?自定義異常??
?????at?interview/compute.MayFailCompute.doCompute(MayFailCompute.java:37)??
?????at?interview/compute.MayFailCompute.doCompute(MayFailCompute.java:14)?????at?interview/version5.MyCacheVersion5$1.call(MyCacheVersion5.java:47)?????at?java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)?????at?java.base/java.util.concurrent.FutureTask.run(FutureTask.java)?????at?interview/version5.MyCacheVersion5.compute(MyCacheVersion5.java:53)?????...?7?more??
?????經(jīng)過修復(fù)之后:??
?????16:09:07.705?[pool-1-thread-83]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?9,重新計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????result?=>?37??
?????16:09:07.705?[pool-1-thread-84]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?84,重新計(jì)算??
?????16:09:07.705?[pool-1-thread-66]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?84,重新計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????16:09:07.705?[pool-1-thread-91]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?9,重新計(jì)算??
?????16:09:07.706?[pool-1-thread-2]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?84,重新計(jì)算??
?????16:09:07.706?[pool-1-thread-3]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?40,重新計(jì)算??
?????16:09:07.706?[pool-1-thread-5]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?84,重新計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????16:09:07.706?[pool-1-thread-91]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?9,重新計(jì)算??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????16:09:07.706?[pool-1-thread-3]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?40,重新計(jì)算??
?????16:09:07.706?[pool-1-thread-5]?INFO?version5.MyCacheVersion5?-?移除緩存Key?=>?84,重新計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????FutureTask?調(diào)用計(jì)算函數(shù)??
?????*/??
}
7. 緩存過期和增加隨機(jī)性
上面處理了緩存污染的問題,下面我們嘗試為整個(gè)高性能緩存添加緩存過期時(shí)間,同時(shí)為防止“緩存雪崩”,增加過期時(shí)間隨機(jī)性,為了方便理解,這里拆分兩個(gè)小部分介紹如何處理。
緩存過期
實(shí)現(xiàn)緩存過期這里要用到定時(shí)線程池 ScheduledExecutorService。我們直接定一個(gè)帶過期時(shí)間的新方法處理。
??
public?V?compute(A?arg,?long?expireTime)?{??
????if?(expireTime?>?0)?{??
????????SCHEDULED_EXECUTOR_SERVICE.schedule(new?Runnable()?{??
???????????? ??
????????????public?void?run()?{??
????????????????//?定期清除緩存的方法??
????????????????expire(arg);??
????????????}??
??
????????????/**??
?????????????*?@description?注意需要同步方法,防止多線程重復(fù)添加定時(shí)任務(wù)??
?????????????*?@param?arg??
?????????????*?@return?void??
?????????????*?@author?xander??
?????????????*?@date?2023/6/20?16:58??
?????????????*/????????????private?synchronized?void?expire(A?arg)?{??
????????????????//?檢查當(dāng)前?key?是否存在??
????????????????Future<V>?vFuture?=?concurrentHashMap.get(arg);??
????????????????//?如果?value?存在,則需要進(jìn)行??
????????????????if(Objects.nonNull(vFuture)){??
????????????????????//如果任務(wù)被取消,此時(shí)需要關(guān)閉對(duì)應(yīng)的定時(shí)任務(wù)??
????????????????????if(vFuture.isDone()?){??
????????????????????????log.warn("future?任務(wù)被取消");??
????????????????????????vFuture.cancel(true);??
????????????????????}??
????????????????????log.warn("過期時(shí)間到了,緩存被清除");??
????????????????????concurrentHashMap.remove(arg);??
????????????????}??
????????????}??
????????},?expireTime,?TimeUnit.MILLISECONDS);??
????}??
????return?doCompute(arg);??
}
增加隨機(jī)性
增加隨機(jī)性的目的是防止緩存在同一個(gè)時(shí)刻大量失效這種情況。增加隨機(jī)性的最簡單方法就是在設(shè)置超時(shí)時(shí)間的時(shí)候給一個(gè)隨機(jī)random值。
??
public?V?compute(A?arg,?long?expireTime,?boolean?isRandom){??
????if(isRandom){??
????????return?compute(?arg,??expireTime);??
????}else{??
????????return?compute(?arg,??expireTime?+?RANDOM.nextInt(1000));??
????}??
}
8. 完整代碼
至此我們整個(gè)高性能緩存構(gòu)建完成,最終的成果代碼如下:
??
/**??
?*?高性能緩存第六版??
?*??
?*?@author?Xander??
?*?@version?v1.0.0??
?*?@Package?:?version6??
?*?@Description?:?高性能緩存第六版??
?*?@Create?on?:?2023/6/20?16:30??
?**/ 4j??
public?class?MyCacheVersion6<A,?V>?{??
??
????private?final?Map<A,?Future<V>>?concurrentHashMap?=?new?ConcurrentHashMap<>();??
????private?static?final?Random?RANDOM?=?new?Random();;??
??
????private?static?final?ScheduledExecutorService?SCHEDULED_EXECUTOR_SERVICE?=?new?ScheduledThreadPoolExecutor(10);??
??
????private?final?ComputeAble?computeAble?=?new?MayFailCompute();??
??
??
????public?V?compute(A?arg)?{??
????????return?doCompute(arg);??
????}??
??
????public?V?compute(A?arg,?long?expireTime)?{??
????????if?(expireTime?>?0)?{??
????????????SCHEDULED_EXECUTOR_SERVICE.schedule(new?Runnable()?{??
???????????????? ??
????????????????public?void?run()?{??
????????????????????//?定期清除緩存的方法??
????????????????????expire(arg);??
????????????????}??
??
????????????????/**??
?????????????????*?@description?注意需要同步方法,防止多線程重復(fù)添加定時(shí)任務(wù)??
?????????????????*?@param?arg??
?????????????????*?@return?void??
?????????????????*?@author?xander??
?????????????????*?@date?2023/6/20?16:58??
?????????????????*/????????????????private?synchronized?void?expire(A?arg)?{??
????????????????????//?檢查當(dāng)前?key?是否存在??
????????????????????Future<V>?vFuture?=?concurrentHashMap.get(arg);??
????????????????????//?如果?value?存在,則需要進(jìn)行??
????????????????????if(Objects.nonNull(vFuture)){??
????????????????????????//如果任務(wù)被取消,此時(shí)需要關(guān)閉對(duì)應(yīng)的定時(shí)任務(wù)??
????????????????????????if(vFuture.isDone()?){??
????????????????????????????log.warn("future?任務(wù)被取消");??
????????????????????????????vFuture.cancel(true);??
????????????????????????}??
????????????????????????log.warn("過期時(shí)間到了,緩存被清除");??
????????????????????????concurrentHashMap.remove(arg);??
????????????????????}??
????????????????}??
????????????},?expireTime,?TimeUnit.MILLISECONDS);??
????????}??
????????return?doCompute(arg);??
????}??
??
????public?V?compute(A?arg,?long?expireTime,?boolean?isRandom){??
????????if(isRandom){??
????????????return?compute(?arg,??expireTime);??
????????}else{??
????????????return?compute(?arg,??expireTime?+?RANDOM.nextInt(1000));??
????????}??
????}??
??
??
????private?V?doCompute(A?arg)?{??
????????//?對(duì)于重復(fù)計(jì)算進(jìn)行處理??
????????while?(true)?{??
????????????Future<V>?result?=?concurrentHashMap.get(arg);??
????????????try?{??
????????????????//?如果獲取不到內(nèi)容,說明不在緩存當(dāng)中??
????????????????if?(Objects.isNull(result))?{??
????????????????????//?此時(shí)利用callAble?線程任務(wù)指定任務(wù)獲取,在獲取到結(jié)果之前線程會(huì)阻塞??
????????????????????FutureTask<V>?future?=?new?FutureTask<>(new?Callable<V>()?{??
???????????????????????? ??
???????????????????????? ("unchecked")??
????????????????????????public?V?call()?throws?Exception?{??
????????????????????????????return?(V)?computeAble.doCompute(arg);??
??
????????????????????????}??
????????????????????});??
????????????????????//把新的future覆蓋之前獲取的future??
????????????????????result?=?future;??
????????????????????//?執(zhí)行??
????????????????????future.run();??
????????????????????System.out.println("FutureTask?調(diào)用計(jì)算函數(shù)");??
????????????????????result?=?concurrentHashMap.putIfAbsent(arg,?result);??
????????????????????//?如果返回null,說明這個(gè)記錄被添加過了??
????????????????????if?(Objects.isNull(result))?{??
????????????????????????System.out.println("其他線程進(jìn)行設(shè)置,重新執(zhí)行計(jì)算");??
????????????????????????//?說明其他線程已經(jīng)設(shè)置過值,這里重新跑一次計(jì)算方法即可直接獲取??
????????????????????????result?=?future;??
????????????????????????//?再重新跑一次??
????????????????????????future.run();??
????????????????????????return?result.get();??
????????????????????}?else?{??
????????????????????????return?result.get();??
????????????????????}??
????????????????}??
????????????????return?result.get();??
????????????}?catch?(CancellationException?cancellationException)?{??
????????????????log.warn("CancellationException?result?=>?{}",?result);??
????????????????//?線程在執(zhí)行過程當(dāng)中有可能被取消??
????????????????//?被取消的時(shí)候不管如何處理,首先需要先從緩存中移除掉污染緩存??
????????????????concurrentHashMap.remove(arg);??
????????????????throw?new?RuntimeException(cancellationException);??
????????????}?catch?(InterruptedException?e)?{??
????????????????log.warn("InterruptedException?result?=>?{}",?result);??
????????????????//?線程被中斷的異常處理??
????????????????concurrentHashMap.remove(arg);??
????????????????throw?new?RuntimeException(e);??
????????????}?catch?(ExecutionException?e)?{??
//????????????log.warn("ExecutionException?result?=>?{}",?result);??
????????????????log.info("移除緩存Key?=>?{},重新計(jì)算",?arg);??
????????????????concurrentHashMap.remove(arg);??
????????????????//?不會(huì)拋出異常,而是重新在下一次循環(huán)中計(jì)算??
//????????????throw?new?RuntimeException(e);??
????????????}?catch?(Exception?e)?{??
????????????????log.warn("Exception?result?=>?{}",?result);??
????????????????concurrentHashMap.remove(arg);??
????????????????//?無法處理的未知異常,直接拋出運(yùn)行時(shí)異常不做任何處理。??
????????????????throw?new?RuntimeException(e);??
????????????}??
??
????????}??
????}??
}
測(cè)試代碼
/**??
?*?緩存過期功能測(cè)試??
?*?@author?Xander??
?*?@version?v1.0.0??
?*?@Package?:?version6??
?*?@Description?:?緩存過期功能測(cè)試??
?*?@Create?on?:?2023/6/20?17:06??
?**/
public?class?Test?{??
??
????public?static?void?main(String[]?args)?throws?InterruptedException?{??
????????ExecutorService?executorService?=?Executors.newFixedThreadPool(100);??
????????MyCacheVersion6<String,?Integer>?myCacheVersion5?=?new?MyCacheVersion6<>();??
????????Random?random?=?new?Random(100);??
????????for?(int?i?=?0;?i?<?1000;?i++)?{??
????????????executorService.submit(()?->?{??
????????????????int?randomInt?=?random.nextInt(100);??
??
????????????????Integer?user?=?myCacheVersion5.compute(String.valueOf(randomInt));??
????????????????System.out.println("result?=>?"?+?user);??
??
??
????????????});??
????????}??
????????executorService.shutdown();??
????}??
}
二、測(cè)試緩存性能
測(cè)試緩存性能的點(diǎn)包含下面的部分:
使用線程池測(cè)試高性能緩存的性能
使用CountDownLatch壓力測(cè)試
線程安全類ThreadSafeFormatter驗(yàn)證CountDownLatch
之前我們的Test測(cè)試都是使用線程池的模式,這里不過多介紹,這里提一下如何使用CountDownLatch進(jìn)行”壓力測(cè)試“,以及使用ThreadSafeFormatter驗(yàn)證CountDownLatch的性能。
??
/**??
?*?ThreadSafeFormatter?*?@author?Xander??
?*?@version?v1.0.0??
?*?@Package?:?com.zxd.interview.mycache.version7??
?*?@Description?:?線程安全?ThreadSafeFormatter??
?*?@Create?on?:?2023/6/22?11:11??
?**/public?class?ThreadSafeFormatter?{??
??
??
????public?static?ThreadLocal<SimpleDateFormat>?dateFormatter?=?new?ThreadLocal<SimpleDateFormat>()?{??
??
????????/**??
?????????*?每個(gè)線程會(huì)調(diào)用本方法一次,用于初始化??
?????????*?@description?每個(gè)線程會(huì)調(diào)用本方法一次,用于初始化??
?????????*?@param??
?????????*?@return?java.text.SimpleDateFormat??
?????????*?@author?xander??
?????????*?@date?2023/6/22?11:30??
?????????*/????????
????????? ??
????????protected?SimpleDateFormat?initialValue()?{??
????????????return?new?SimpleDateFormat("mm:ss");??
????????}??
??
????????/**??
?????????*?首次調(diào)用本方法時(shí),會(huì)調(diào)用initialValue();后面的調(diào)用會(huì)返回第一次創(chuàng)建的值??
?????????*?@description?首次調(diào)用本方法時(shí),會(huì)調(diào)用initialValue();后面的調(diào)用會(huì)返回第一次創(chuàng)建的值??
?????????*?@param??
?????????*?@return?java.text.SimpleDateFormat??
?????????*?@author?xander??
?????????*?@date?2023/6/22?11:30??
?????????*/????????
???????? ??
????????public?SimpleDateFormat?get()?{??
????????????return?super.get();??
????????}??
????};??
}??
/**??
?*?整體性能測(cè)試??
?*?@author?Xander??
?*?@version?v1.0.0??
?*?@Package?:?com.zxd.interview.mycache.version6??
?*?@Description?:?整體性能測(cè)試??
?*?@Create?on?:?2023/6/20?17:06??
?**/public?class?Test?{??
??
????public?static?void?main(String[]?args)?throws?InterruptedException?{??
????????long?beginTime?=?System.currentTimeMillis();??
????????ExecutorService?executorService?=?Executors.newFixedThreadPool(100);??
????????MyCacheVersion6<String,?Integer>?myCacheVersion5?=?new?MyCacheVersion6<>();??
????????Random?random?=?new?Random(200);??
????????CountDownLatch?countDownLatch?=?new?CountDownLatch(1);??
????????for?(int?i?=?0;?i?<?100;?i++)?{??
????????????executorService.submit(()?->?{??
????????????????int?randomInt?=?random.nextInt(100);??
??
????????????????try?{??
????????????????????countDownLatch.await();??
????????????????????//?從線程安全的集合當(dāng)中取出當(dāng)前時(shí)間??
????????????????????SimpleDateFormat?simpleDateFormat?=?ThreadSafeFormatter.dateFormatter.get();??
????????????????????System.out.println("simpleDateFormat?=>?"+?simpleDateFormat.format(new?Date()));??
????????????????}?catch?(InterruptedException?e)?{??
????????????????????e.printStackTrace();??
????????????????}??
????????????????Integer?user?=?myCacheVersion5.compute(String.valueOf(randomInt),?5000);??
????????????????System.out.println("result?=>?"?+?user);??
????????????});??
????????}??
????????//?假設(shè)此時(shí)所有的請(qǐng)求需要5秒時(shí)間準(zhǔn)備。??
????????Thread.sleep(1000);??
????????countDownLatch.countDown();??
????????executorService.shutdown();??
????????long?endTime?=?System.currentTimeMillis();??
????????//?如果線程池沒有停止一直死循環(huán)??
????????while(!executorService.isTerminated()){??
??
????????}??
????????System.out.println("最終時(shí)間"?+?(endTime?-?beginTime));??
????}/**??
??
?????10:59:34.521?[pool-2-thread-3]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?future?任務(wù)被取消??
?????10:59:34.522?[pool-2-thread-3]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?過期時(shí)間到了,緩存被清除??
?????10:59:34.521?[pool-2-thread-4]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?future?任務(wù)被取消??
?????10:59:34.522?[pool-2-thread-4]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?過期時(shí)間到了,緩存被清除??
?????10:59:34.521?[pool-2-thread-1]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?future?任務(wù)被取消??
?????10:59:34.522?[pool-2-thread-1]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?過期時(shí)間到了,緩存被清除??
?????10:59:34.521?[pool-2-thread-8]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?future?任務(wù)被取消??
?????10:59:34.522?[pool-2-thread-8]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?過期時(shí)間到了,緩存被清除??
?????10:59:34.521?[pool-2-thread-7]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?過期時(shí)間到了,緩存被清除??
?????10:59:34.521?[pool-2-thread-2]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?future?任務(wù)被取消??
?????10:59:34.522?[pool-2-thread-2]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?過期時(shí)間到了,緩存被清除??
?????10:59:34.522?[pool-2-thread-5]?WARN?com.zxd.interview.mycache.version6.MyCacheVersion6?-?過期時(shí)間到了,緩存被清除??
??
?????最終時(shí)間146??
??
?????如果修改為多個(gè)時(shí)間同時(shí)發(fā)起請(qǐng)求:??
?????最終時(shí)間1074?-?1000?主線程的睡眠時(shí)間?=?74??
??
??
?????可以看到時(shí)間點(diǎn)都是在同一個(gè)分秒,可以人為countDownlatch是生效的??
?????simpleDateFormat?=>?14:50??
?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50?????simpleDateFormat?=>?14:50????
??????*/??
}
}
三、寫在最后
這個(gè)小demo在自己實(shí)際上手寫的時(shí)候還是有不少卡殼的地方,如果有疑問或者改進(jìn)意見歡迎討論。