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