最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

《玩轉(zhuǎn)Java并發(fā)工具、精通JUC、成為并發(fā)多面手》構(gòu)建高性能緩存

2023-06-27 09:29 作者:懶時(shí)小窩  | 我要投稿

引言

《玩轉(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)建的梳理思路如下:


    1. 使用最簡單的HashMap


    • 高并發(fā)訪問重復(fù)計(jì)算性能問題

    • 復(fù)用性能較差的問題

    1. 分析HashMap實(shí)現(xiàn)的問題


    • 解決復(fù)用性能較差的問題

    1. 裝飾模式抽象計(jì)算業(yè)務(wù)


    • 防止業(yè)務(wù)重復(fù)計(jì)算

    • 如何處理高并發(fā)訪問重復(fù)計(jì)算性能問題

    1. 使用Future改寫計(jì)算實(shí)現(xiàn)接口


    1. 增加泛型


    • 防止同一個(gè)時(shí)刻大量緩存過期增加系統(tǒng)壓力

    • 防止緩存污染

    • 為什么需要緩存過期?

    • 為什么要增加隨機(jī)性?

      1. 緩存過期和增加隨機(jī)性


      1. 整體測(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ù)判斷??
    ?*/
    ??
    @Slf4j??
    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)證線程安全問題??
    ?*/
    ??
    @Slf4j??
    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>{??
    ????@Override??
    ????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??
    ?**/
    @Slf4j??
    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í)先用具體類型代替泛型。

    1. 首先把Map的value存儲(chǔ)改為存儲(chǔ)Future< V > 結(jié)果,并且把HashMap改為線程安全的ConcurrentHashMap。

    private?final?Map<String,?Future<Integer>>?concurrentHashMap?=?new?ConcurrentHashMap<>();

    1. 構(gòu)建 FutureTask 對(duì)象,這里使用 Lambda 表達(dá)式直接調(diào)用 doCompute 方法。

    FutureTask<Integer>?future?=?new?FutureTask<>(()?->?doCompute(userId));

    1. 在計(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??
    ?**/
    @Slf4j??
    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??
    ?**/
    @Slf4j??
    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>()?{??
    ????????????????@Override??
    ????????????????@SuppressWarnings("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??
    ?????*/
    ????@Override??
    ????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)度:

    • CancellationExceptionInterruptedException 基本都是人為操作,這時(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??
    ?**/
    @Slf4j??
    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>()?{??
    ????????????????????????@Override??
    ????????????????????????@SuppressWarnings("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()?{??
    ????????????@Override??
    ????????????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??
    ?**/
    @Slf4j??
    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()?{??
    ????????????????@Override??
    ????????????????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>()?{??
    ????????????????????????@Override??
    ????????????????????????@SuppressWarnings("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??
    ?????????*/
    ????????
    ?????????@Override??
    ????????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??
    ?????????*/
    ????????
    ????????@Override??
    ????????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)意見歡迎討論。


    《玩轉(zhuǎn)Java并發(fā)工具、精通JUC、成為并發(fā)多面手》構(gòu)建高性能緩存的評(píng)論 (共 條)

    分享到微博請(qǐng)遵守國家法律
    吐鲁番市| 房产| 乌恰县| 阳城县| 确山县| 临颍县| 浦江县| 克山县| 汤原县| 湘潭县| 靖宇县| 连云港市| 收藏| 杭州市| 崇阳县| 额尔古纳市| 拉萨市| 延安市| 弋阳县| 小金县| 常山县| 菏泽市| 永靖县| 崇义县| 墨玉县| 诏安县| 南木林县| 定远县| 墨竹工卡县| 麻城市| 香港 | 禹城市| 太谷县| 依安县| 衢州市| 滕州市| 嵩明县| 视频| 汉阴县| 运城市| 都安|