摘要:是多線程包里的一個(gè)常見工具類,通過使用它可以借助線程能力極大提升處理響應(yīng)速度,且實(shí)現(xiàn)方式非常優(yōu)雅。主線程處于狀態(tài),直到的值數(shù)減到,則主線程繼續(xù)執(zhí)行。此時(shí)必須使用線程池,并限定最大可處理線程數(shù)量,否則服務(wù)器不穩(wěn)定性會(huì)大福提升。
countdownlatch是java多線程包c(diǎn)oncurrent里的一個(gè)常見工具類,通過使用它可以借助線程能力極大提升處理響應(yīng)速度,且實(shí)現(xiàn)方式非常優(yōu)雅。今天我們用一個(gè)實(shí)際案例和大家來講解一下如何使用以及需要特別注意的點(diǎn)。
由于線程類的東西都比較抽象,我們換一種講解思路,先講解決問題的案例,然后再解釋下原理。
假設(shè)在微服務(wù)架構(gòu)中,A服務(wù)會(huì)調(diào)用B服務(wù)處理一些事情,且每處理一次業(yè)務(wù),A可能要調(diào)用B多次處理邏輯相同但數(shù)據(jù)不同的事情。為了提升整個(gè)鏈路的處理速度,我們自然會(huì)想到是否可以把A調(diào)用B的各個(gè)請求組成一個(gè)批次,這樣A服務(wù)只需要調(diào)用B服務(wù)一次,等B服務(wù)處理完一起返回即可,省了多次網(wǎng)絡(luò)傳輸?shù)臅r(shí)間。代碼如下:
/** * 批次請求處理服務(wù) * @param batchRequests 批次請求對象列表 * @return */ public Listdeal(List batchRequests){ List resultList = new ArrayList<>(); if(batchRequests != null){ for(DealRequest request : batchRequests){ //遍歷順序處理單個(gè)請求 resultList.add(process(request)); } } return resultList; }
但是B服務(wù)順序處理批次里每一個(gè)請求的時(shí)間并沒有節(jié)省,假設(shè)批次里有3個(gè)請求,一個(gè)請求平均耗時(shí)100MS,則B服務(wù)還是要花費(fèi)300MS來處理完。有什么辦法能立刻簡單提升3倍處理速度,令總花費(fèi)時(shí)間只需要100MS?到我們的大將countdownlatch出場了!代碼如下:
/** * 使用countdownlatch的批次請求處理服務(wù) * @param batchRequests 批次請求對象列表 * @return */ public ListcountDownDeal(List batchRequests){ //定義線程安全的處理結(jié)果列表 List countDownResultList = Collections.synchronizedList(new ArrayList ()); if(batchRequests != null){ //定義countdownlatch線程數(shù),有多少個(gè)請求,我們就定義多少個(gè) CountDownLatch runningThreadNum = new CountDownLatch(batchRequests.size()); for(DealRequest request : batchRequests){ //循環(huán)遍歷請求,并實(shí)例化線程(構(gòu)造函數(shù)傳入CountDownLatch類型的runningThreadNum),立刻啟動(dòng) DealWorker dealWorker = new DealWorker(request, runningThreadNum, countDownResultList); new Thread(dealWorker).start(); } try { //調(diào)用CountDownLatch的await方法則當(dāng)前主線程會(huì)等待,直到CountDownLatch類型的runningThreadNum清0 //每個(gè)DealWorker處理完成會(huì)對runningThreadNum減1 //如果等待1分鐘后當(dāng)前主線程都等不到runningThreadNum清0,則認(rèn)為超時(shí),返回false,可以根據(jù)實(shí)際情況選擇處理或忽視 runningThreadNum.await(1, TimeUnit.MINUTES); } catch (InterruptedException e) { //此處簡化處理,非正常中斷應(yīng)該拋出異?;蚍祷劐e(cuò)誤結(jié)果 return null; } } return countDownResultList; } /** * 線程請求處理類 * */ private class DealWorker implements Runnable { /** 正在運(yùn)行的線程數(shù) */ private CountDownLatch runningThreadNum; /**待處理請求*/ private DealRequest request; /**待返回結(jié)果列表*/ private List countDownResultList; /** * 構(gòu)造函數(shù) * @param request 待處理請求 * @param runningThreadNum 正在運(yùn)行的線程數(shù) * @param countDownResultList 待返回結(jié)果列表 */ private DealWorker(DealRequest request, CountDownLatch runningThreadNum, List countDownResultList) { this.request = request; this.runningThreadNum = runningThreadNum; this.countDownResultList = countDownResultList; } @Override public void run() { try{ this.countDownResultList.add(process(this.request)); }finally{ //當(dāng)前線程處理完成,runningThreadNum線程數(shù)減1,此操作必須在finally中完成,避免處理異常后造成runningThreadNum線程數(shù)無法清0 this.runningThreadNum.countDown(); } } }
是不是很簡單?下圖和上面的代碼又做了一個(gè)對應(yīng),假設(shè)有3個(gè)請求,則啟動(dòng)3個(gè)子線程DealWorker,并實(shí)例化值數(shù)等于3的CountDownLatch。每當(dāng)一個(gè)子線程處理完成后,則調(diào)用countDown操作減1。主線程處于awaiting狀態(tài),直到CountDownLatch的值數(shù)減到0,則主線程繼續(xù)resume執(zhí)行。
在API中是這樣描述的:
用給定的計(jì)數(shù) 初始化 CountDownLatch。由于調(diào)用了 countDown() 方法,所以在當(dāng)前計(jì)數(shù)到達(dá)零之前,await 方法會(huì)一直受阻塞。之后,會(huì)釋放所有等待的線程,await 的所有后續(xù)調(diào)用都將立即返回。這種現(xiàn)象只出現(xiàn)一次——計(jì)數(shù)無法被重置。如果需要重置計(jì)數(shù),請考慮使用 CyclicBarrier。
經(jīng)典的java并發(fā)編程實(shí)戰(zhàn)一書中做了更深入的定義:CountDownLatch屬于閉鎖的范疇,閉鎖是一種同步工具類,可以延遲線程的進(jìn)度直到其到達(dá)終止?fàn)顟B(tài)。閉鎖的作用相當(dāng)于一扇門:在閉鎖到達(dá)結(jié)束狀態(tài)之前(上面代碼中的runningThreadNumq清0),這扇門一直是關(guān)閉的,并且沒有任何線程能通過(上面代碼中的主線程一直await),當(dāng)?shù)竭_(dá)結(jié)束狀態(tài)時(shí),這扇門會(huì)打開并允許所有線程通過(上面代碼中的主線程可以繼續(xù)執(zhí)行)。當(dāng)閉鎖到達(dá)結(jié)束狀態(tài)后,將不會(huì)再改變狀態(tài),因此這扇門將永遠(yuǎn)保持打開狀態(tài)。
像FutureTask,Semaphore這類在concurrent包里的類也屬于閉鎖,不過它們和CountDownLatch的應(yīng)用場景還是有差別的,這個(gè)我們在后面的文章里再細(xì)說。
使用CountDownLatch有哪些需要注意的點(diǎn)批次請求之間不能有執(zhí)行順序要求,否則多個(gè)線程并發(fā)處理無法保證請求執(zhí)行順序
各線程都要操作的結(jié)果列表必須是線程安全的,比如上面代碼范例的countDownResultList
各子線程的countDown操作要在finally中執(zhí)行,確保一定可以執(zhí)行
主線程的await操作需要設(shè)置超時(shí)時(shí)間,避免因子線程處理異常而長時(shí)間一直等待,如果中斷需要拋出異常或返回錯(cuò)誤結(jié)果
使用CountDownLatch提高批次處理速度的問題如果一個(gè)批次請求數(shù)很多,會(huì)瞬間占用服務(wù)器大量線程。此時(shí)必須使用線程池,并限定最大可處理線程數(shù)量,否則服務(wù)器不穩(wěn)定性會(huì)大福提升。
主線程和子線程間的數(shù)據(jù)傳輸變得困難,稍不注意會(huì)造成線程不安全的問題,且代碼可讀性有一定下降
下一篇文章我們講講FutureTask的應(yīng)用場景,謝謝!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/67666.html
摘要:所有示例代碼請見下載于基本概念并發(fā)同時(shí)擁有兩個(gè)或者多個(gè)線程,如果程序在單核處理器上運(yùn)行多個(gè)線程將交替地?fù)Q入或者換出內(nèi)存這些線程是同時(shí)存在的,每個(gè)線程都處于執(zhí)行過程中的某個(gè)狀態(tài),如果運(yùn)行在多核處理器上此時(shí),程序中的每個(gè)線程都 所有示例代碼,請見/下載于 https://github.com/Wasabi1234... showImg(https://upload-images.jians...
摘要:方法由兩個(gè)參數(shù),表示期望的值,表示要給設(shè)置的新值。操作包含三個(gè)操作數(shù)內(nèi)存位置預(yù)期原值和新值。如果處的值尚未同時(shí)更改,則操作成功。中就使用了這樣的操作。上面操作還有一點(diǎn)是將事務(wù)范圍縮小了,也提升了系統(tǒng)并發(fā)處理的性能。 這是java高并發(fā)系列第21篇文章。 本文主要內(nèi)容 從網(wǎng)站計(jì)數(shù)器實(shí)現(xiàn)中一步步引出CAS操作 介紹java中的CAS及CAS可能存在的問題 悲觀鎖和樂觀鎖的一些介紹及數(shù)據(jù)庫...
摘要:所以得出結(jié)論需要分配較多的線程進(jìn)行讀數(shù)據(jù),較少的線程進(jìn)行寫數(shù)據(jù)。注意多線程編程對實(shí)際環(huán)境和需求有很大的依賴,需要根據(jù)實(shí)際的需求情況對各個(gè)參數(shù)做調(diào)整。 背景 最近對于 Java 多線程做了一段時(shí)間的學(xué)習(xí),筆者一直認(rèn)為,學(xué)習(xí)東西就是要應(yīng)用到實(shí)際的業(yè)務(wù)需求中的。否則要么無法深入理解,要么硬生生地套用技術(shù)只是達(dá)到炫技的效果。 不過筆者仍舊認(rèn)為自己對于多線程掌握不夠熟練,不敢輕易應(yīng)用到生產(chǎn)代碼中...
摘要:今天給大家總結(jié)一下,面試中出鏡率很高的幾個(gè)多線程面試題,希望對大家學(xué)習(xí)和面試都能有所幫助。指令重排在單線程環(huán)境下不會(huì)出先問題,但是在多線程環(huán)境下會(huì)導(dǎo)致一個(gè)線程獲得還沒有初始化的實(shí)例。使用可以禁止的指令重排,保證在多線程環(huán)境下也能正常運(yùn)行。 下面最近發(fā)的一些并發(fā)編程的文章匯總,通過閱讀這些文章大家再看大廠面試中的并發(fā)編程問題就沒有那么頭疼了。今天給大家總結(jié)一下,面試中出鏡率很高的幾個(gè)多線...
閱讀 3484·2021-09-22 15:02
閱讀 3536·2021-09-02 15:21
閱讀 2145·2019-08-30 15:55
閱讀 2796·2019-08-30 15:44
閱讀 794·2019-08-29 16:56
閱讀 2426·2019-08-23 18:22
閱讀 3354·2019-08-23 12:20
閱讀 3102·2019-08-23 11:28