如何讓多線程步調一致?

前幾天老板突然匆匆忙忙的過來說對賬系統最近越來越慢了,能不能快速優化一下?我了解了對賬系統的業務后,發現還是挺簡單的,用戶通過在線商城下單,會生成電子訂單,保存在訂單庫。之后物流會生成派送單給用戶發貨,派送單保存在派送單庫。為了防止漏送或重復派送,對賬系統每天還會校驗是否存在異常訂單。
對賬系統的處理邏輯很簡單,你可以參考下面的對賬系統流程圖。目前的對賬系統處理邏輯是,首先查詢訂單,然后查詢派送單,然后對比訂單和派送單,將差異寫入差異庫。
在這里插入圖片描述
對賬系統的代碼抽象之后也很簡單,核心代碼如下,就是在一個單線程里循環查詢訂單、派送單,然后執行對賬,最后寫入差異庫。

while(存在未對賬的訂單){//查詢未對賬訂單pos = getPRders();//查詢派送單dos = getDOrder();//執行對賬操作diff = chech(pos,dos);//差異寫入差異庫save(diff);
}

利用并行優化對賬系統

老板要我優化性能,那我就首先要找到這個對賬系統的瓶頸所在。
目前的對賬系統由于訂單量和派送單量巨大,所以查詢對賬訂單getPOrders和查詢派送單getDOrders相對較慢,那有沒有什么辦法可以快速優化一下呢?目前對賬系統是單線程執行的。圖形化后是下面這個樣子,對于串行化的系統性能優化,首先想到的是能否利用多線程并行處理
在這里插入圖片描述所以這里你應該所以,這里你應該能夠看出來這個對賬系統里的瓶頸,查詢未對賬訂單getPOrder和查詢派送訂單getDOrder是否可以并行處理呢?顯然是可以的,因為這兩個操作并沒有先后順序的依賴,這兩個最耗時的操作并行執行之后,執行過程如下圖所示。對比一下單線程的執行示意圖,你會發現同等時間里并行執行的吞吐量近乎單線程的兩倍。提升效果還是相對明顯的。
在這里插入圖片描述
思路有了,下面我們再來看看如何利用代碼實現。在下面的代碼中,我們創建了兩個線程T1和T2,并行執行查詢未對賬訂單getPOrder和查詢派送訂單getDOrder這兩個操作。在主線程中執行對賬操作check和差異寫入save兩個操作,不過要注意的是,主線程需要等待線程T1和T2執行完才能執行check和save兩個操作。為此,我們通過調用T1.join和T2.join來實現等待。當T1和T2線程退出時。調用T1.join和T2.join的主線程就會從阻塞狀態被喚醒,從而執行之后的check和save。

while(存在未對賬訂單){//查詢未對賬訂單Thread T1 = new Thread(() ->{pos = getPOrder();});T1.start();//查詢派送訂單Thread T2 = new Thread(()->{dos = getDOrder();});T2.start();//等待T1 T2的結果T1.join();T2.join();//執行對賬操作diff = check(pos,dos);save(diff);
}

用CountDownLatch實現線程等待

經過上面的優化之后,基本可以和老板匯報收工了,但是有點美中不足,相信你也發現了,while循環里面每次都會創建新的線程,而創建線程可是個耗時的操作,所以最好是創建出來的線程能夠循環利用。估計這時候你已經想到線程池了,是的,線程池就能解決這個問題。
而下面的代碼就是利用線程池優化之后的,我們首先創建一個固定大小為2的線程池,之后在while循環里面重復利用,一切看上去都很順利。但是有個問題好像無解了,那就是主線程如何知道getPOrder和getDOrder這兩個操作什么時候執行完?前面的主線程通過調用線程T1和T2的join方法來等待線程T1和T2退出,但是在線程池的方案里,線程根本不會退出,所以join方法已經失效了。

//創建兩個線程的線程池
Executor executor = Executor.newFixedThreadPool(2);
while(存在未對賬訂單){//查詢未對賬訂單executor.execute(() ->{pos = getPOrder();});//查詢派送訂單executor.execute(()->{dos = getDOrder();});/* 如何實現線程等待呢?*///執行對賬操作diff = check(pos,dos);save(diff);
}

那如何解決這個問題呢?你可以開動腦筋想出很多辦法。最直接的辦法是弄一個計數器,初始值設置成2,當執行完pos = getPOrder();這個操作之后,計數器減1,執行完dos = getDOrder();之后,計數器也減1,在主線程里,等待計數器等于零,當計數器等于零時,說明這兩個查詢操作執行完了。等待計數器等于零其實就是一個條件變量,用管程實現起來也很簡單。
不過我并不建議你在實際項目中去實現上面的方案,因為Java并發包里已經提供了實現類似功能的工具類:CountDownLatch,這里我們可以直接使用。下面的代碼示例中,在while循環里面,我們首先創建了一個CountDownLatch,計數器的初始值等于2。之后在pos = getPOrder();和dos = getDOrder();兩個語句的后面對計數器執行減1操作。這個對計數器減1的操作是通過調用latch.countDown()來實現的。在主線程中,我們通過調用latch.await()來實現對計數器等于0的等待。

Executor executor = Executor.newFixedThreadPool(2);
while(存在未對賬訂單){//計數器初始化未2CountDownLatch latch = new CountDownLatch(2);//查詢未對賬訂單executor.execute(() ->{pos = getPOrder();latch.countDown();});//查詢派送訂單executor.execute(()->{dos = getDOrder();latch.countDown()});//等待連個查詢操作結束latch.await();//執行對賬操作diff = check(pos,dos);save(diff);
}

進一步優化性能

經過上面的重重優化之后,長出一口氣,終于可以交付了。不過在交付之前還需要再次審視一番,看還有沒有優化的余地,仔細看還是有的。
前面我們將getPOrder和getDOrder這兩個查詢操作并行了,但這兩個查詢操作和對賬操作check save之間還是串行的。很顯然,這兩個查詢操作和對賬操作也是可以并行的。也就是說,在執行對賬操作的時候,可以同時去執行下一輪的查詢操作,這個過程可以形象的描述為下面這幅圖。
在這里插入圖片描述
那接下來我們再來思考一下如何實現這步優化,兩次查詢操作都能夠和對賬操作并行。對賬操作還依賴查詢操作的結果,這明顯有點生產者-消費者的意思。兩次查詢操作是生產者,對賬操作是消費者。既然是生產者-消費者模型,那就需要有個隊列來保存生產者生產數據,而消費者則從這個隊列消費數據。
不過針對對賬這個項目,我設計了兩個隊列,并且兩個隊列的元素之間還有對應關系,具體如下圖所示,查詢訂單查詢操作將訂單查詢結果插入訂單隊列,派送單查詢操作將派送單插入派送單隊列,這兩個隊列的元素之間是有一一對應的關系的。兩個隊列的好處是,對賬操作可以每次從訂單隊列出一個元素,從派送單隊列出一個元素,然后對這兩個元素執行對賬操作,這樣數據一定不會亂掉。
在這里插入圖片描述
下面再來看看如何利用雙隊列來實現完全的并行。一個最直接的想法是,一個線程T1執行訂單的查詢工作,一個線程T2執行派送單的查詢工作,當線程T1和T2都各自生產完一條數據的時候,就通知線程T3執行對賬操作。這個想法雖然看上去很簡單,但其實還隱藏著一個條件,那就是線程T1和線程T2的工作步調要一致,不能一個跑的太快,一個跑的太慢。只有這樣才能做到各自生產完一條數據的時候,通知線程T3。
下面這幅圖形象的描述了上面的意圖,線程T1和線程T2只有都生產完了一條數據的時候,才能一起向下執行,也就是說線程T1和線程T2還要相互等待,步調要一致。同時線程T1和T2都生產完一條數據的時候,還要能夠通知線程T3執行對賬操作。
在這里插入圖片描述

用CyclicBarrier實現線程同步

下面我們就來實現上面提到的方案,這兩個方案的難點有兩個,一個是線程T1和T2要做到步調一致,另一個是要能夠通知到線程T3。
你依然可以利用一個計數器來解決這兩個難點。計數器初始化為2,線程T1和T2生產完一條數據,都將計數器減1,如果計數器大于0,則線程T1或T2等待,如果計數器等于零,則通知線程T3,并喚醒等待的線程T1和T2。與此同時,將計數器重新置為2。這樣線程T1和T2生產下一條數據的時候,就可以繼續使用這個計數器了。
同樣,還是建議你不要在實際項目中這么做,因為Java并發包里也已經提供了相關的工具類:CyclicBarrier。下面的代碼中,我們首先創建了一個計數器初始值為2的CyclicBarrier,你需要注意的是創建CyclicBarrier的時候我們還需要傳入一個回調函數,當計數器減到0的時候會調用這個回調函數。
線程T1負責查詢訂單,當查詢出一條的時候調用barrier.await()來將計數器減1,同時等待計數器變成0。線程T2負責查詢派送訂單,當查詢出一條時也調用barrier.await()來將計數器減1,同時等待計數器變成0,當T1和T2都調用barrier.await()的時候,計數器會減到0,當T1和T2,此時T1和T2就可以執行下一條語句了,同時還會調用的回調函數來執行對賬操作。
非常值得一提的是,CyclicBarrier的計數器有自動重置功能,當減到零的時候會自動重置回你設置的初始值,這個功能看用起來實在太方便了。

//訂單隊列
Vector<P> pos;
//派送單隊列
Vector<P> dos;
//執行回調的線程池
Executor executor = Executor.newFixedThreadPool(1);
final CyclicBarrier barrier = new CyclicBarrier(2,()->{executor.execute(() -> check());});void check(){P p = pos.remove(0);D d = dos.remove(0);//執行對賬操作diff = check(p,d);save(diff);
}void checkAll(){//查詢未對賬訂單Thread T1 = new Thread(() ->{while(存在未對賬訂單){//查詢訂單庫pos.add(getPOrder());//等待barrier.await();}});T1.start();//查詢派送訂單Thread T2 = new Thread(() ->{while(存在未對賬訂單){//查詢訂單庫pos.add(getDOrder());//等待barrier.await();}});T2.start();
}

總結

CountDownLatch和CyclicBarrier是Java并發包提供的兩個非常應用的線程同步工具類,這兩個工具類的用法區別在這里還是有必要再強調一下的。CountDownLatch主要用于解決一個線程等待多個線程的場景可以類比于旅游團長要等待所有的旅客到齊才能去下一個景點,而CyclicBarrier是一組線程之間互相等待,更像是幾個驢友之間的不離不棄。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/42344.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/42344.shtml
英文地址,請注明出處:http://en.pswp.cn/news/42344.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Redis - 數據類型映射底層結構

簡介 從數據類型上體現就是&#xff0c;同一個數據類型&#xff0c;在不同的情況下會使用不同的編碼類型&#xff0c;底層所使用的的數據結構也不相同。 字符串對象 字符串對象的編碼可以是 int、raw 和 embstr 三者之一。 embstr 編碼是專門用于保存簡短字符串的一種優化編…

每日一學——無線基礎知識

無線局域網&#xff08;Wireless Local Area Network&#xff0c;簡稱 WLAN&#xff09;是一種使用無線通信技術連接多個無線終端設備的局域網。它通常基于無線電波傳輸數據&#xff0c;并使用無線接入點&#xff08;Access Point&#xff0c;簡稱 AP&#xff09;來連接無線設備…

網絡安全--負載均衡

負載均衡 webshell實踐 一、負載均衡配置 1.在全局的http下寫下它&#xff1a; upstream nginx_boot{# 30s內檢查心跳發送兩次包&#xff0c;未回復就代表該機器宕機&#xff0c;請求分發權重比為1:2server 192.168.0.000:8080 weight100 max_fails2 fail_timeout30s; ser…

LeetCode150道面試經典題-- 合并兩個有序鏈表(簡單)

1.題目 將兩個升序鏈表合并為一個新的 升序 鏈表并返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。 2.示例 示例 1&#xff1a; 輸入&#xff1a;l1 [1,2,4], l2 [1,3,4] 輸出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a; 輸入&#xff1a;l1 [], l2 [] 輸…

k8s 中快速啟動curl pod 做api test

場景 k8s上運行的pod需要進行api測試,由于開發使用的鏡像都是最小化構建,不能保證現有的pod中一定有curl工具,于是需要啟動一個帶有curl工具的測試pod專門進行api測試 指令 kubectl run curl-test-pod --imagecurlimages/curl -n {namespace} -i --tty -- sh上述指令實現在指…

“一日之際在于晨”,歡迎蒞臨WAVE SUMMIT上午場:Arm 虛擬硬件早餐交流會

8月16日&#xff0c;盛夏的北京將迎來第九屆WAVE SUMMIT深度學習開發者大會。在峰會主論壇正式開啟前&#xff0c;讓我們先用一份精美的元氣早餐&#xff0c;和一場“Arm虛擬硬件交流會”&#xff0c;喚醒各位開發小伙伴的開發魂&#xff01; 8月16日&#xff0c;WAVE SUMMIT大…

時序預測 | MATLAB實現WOA-CNN-LSTM鯨魚算法優化卷積長短期記憶神經網絡時間序列預測

時序預測 | MATLAB實現WOA-CNN-LSTM鯨魚算法優化卷積長短期記憶神經網絡時間序列預測 目錄 時序預測 | MATLAB實現WOA-CNN-LSTM鯨魚算法優化卷積長短期記憶神經網絡時間序列預測預測效果基本介紹模型描述程序設計學習總結參考資料 預測效果 基本介紹 時序預測 | MATLAB實現WOA-…

華為OD真題--字符串中最小的整數和--帶答案

1. 華為OD機考題 答案 2023華為OD統一考試&#xff08;AB卷&#xff09;題庫清單-帶答案&#xff08;持續更新&#xff09; 2023年華為OD真題機考題庫大全-帶答案&#xff08;持續更新&#xff09; 2. 面試題 一手真實java面試題&#xff1a;2023年各大公司java面試真題匯總--…

java導入excel圖片處理

直接看代碼吧&#xff0c;主要邏輯吧excel的圖片拿到 壓縮上傳獲取url // 將文件轉成XSSFWorkbook工作簿XSSFWorkbook wb new XSSFWorkbook(uploadFile);// 獲取工作薄中第一個excel表格XSSFSheet sheet wb.getSheetAt(0);// 核心&#xff1a;&#xff1a;&#xff1a;獲取ex…

R語言APSIM模型進階應用與參數優化、批量模擬實踐技術

隨著數字農業和智慧農業的發展&#xff0c;基于過程的農業生產系統模型在模擬作物對氣候變化的響應與適應、農田管理優化、作物品種和株型篩選、農田固碳和溫室氣體排放等領域扮演著越來越重要的作用。APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物生…

《論文閱讀14》FAST-LIO

一、論文 研究領域&#xff1a;激光雷達慣性測距框架論文&#xff1a;FAST-LIO: A Fast, Robust LiDAR-inertial Odometry Package by Tightly-Coupled Iterated Kalman Filter IEEE Robotics and Automation Letters, 2021 香港大學火星實驗室 論文鏈接論文github 二、論文概…

LeetCode49.字母異味詞分組

我一開始的思路就是用1個hashmap<Integer,List<String>>,Integer存的的是字符串所有字母ASCLL值的和&#xff0c;List里面放異位字符串&#xff0c;但是不是異位的字符串的ascll值也可能相同比如acd和abe&#xff0c;所以這個hashmap只能降低一點時間復雜度我還是要…

Vue--》打造個性化醫療服務的醫院預約系統(六)

今天開始使用 vue3 + ts 搭建一個醫院預約系統的前臺頁面,因為文章會將項目的每一個地方代碼的書寫都會講解到,所以本項目會分成好幾篇文章進行講解,我會在最后一篇文章中會將項目代碼開源到我的GithHub上,大家可以自行去進行下載運行,希望本文章對有幫助的朋友們能多多關…

Web APIs 第六天

正則表達式介紹語法元字符修飾符 一.正則表達式介紹 ① 簡介 用來匹配字符串中字符組合的模式在JavaScript中&#xff0c;正則表達式也是對象通常用來查找&#xff0c;替換那些符合正則表達式的文本&#xff0c;許多語言都支持正則表達式 ② 使用場景 驗證表單&#xff1a…

算法通關村第4關【白銀】| 棧的經典算法問題

1.括號匹配問題 思路&#xff1a;將左括號壓入棧中&#xff0c;遍歷字符串&#xff0c;當遇到右括號就出棧&#xff0c;判斷是否是匹配的一對&#xff0c;不是就返回false&#xff08;因為按照順序所以當遇到右括號出棧一定要是匹配的&#xff09;。使用Map來簡化ifelse clas…

編寫一套工具庫并上傳NPM

你的 工具箱 開箱即可用的 directive\utils&#xff0c; 說明&#xff1a;vue3-directive-tools 是一個方便在 Vue 3 Ts 項目中快速使用的 directive、tool 的 npm 插件。它允許您輕松地在項目中添加多種功能&#xff0c;它采用 Ts 方式開發&#xff0c;與 Vue3 更加搭配 npm&…

系統架構設計師---2017年上午試題1答案詳解

2017年上午試題1答案詳解 某計算機系統采用5級流水線結構執行指令,設每條指令的執行由取指令(2?t)、分析指令(1?t)、取操作數(3?t)、運算(1?t)和寫回結果(2?t)組成,并分別用5個子部完成,該流水線的最大吞吐率為(1);若連續向流水線輸入10條指令,則該流水線的加速比為(…

問道管理:放量打拐什么意思?常見的放量打拐三種形態?

成交量一直是股票交易中比較重要的目標&#xff0c;那么&#xff0c;放量打拐是什么意思&#xff1f;常見的放量打拐三種形狀是什么&#xff1f;下面問道管理為我們預備了相關內容&#xff0c;以供參閱。 放量打拐什么意思&#xff1f; 放量是指股票成交量與前幾個交易日比較顯…

安裝和配置 Ansible

安裝和配置 Ansible 按照下方所述&#xff0c;在控制節點 control.area12.example.com 上安裝和配置 Ansible&#xff1a; 安裝所需的軟件包 創建名為 /home/curtis/ansible/inventory 的靜態清單文件&#xff0c;以滿足以下要求&#xff1a; node1 是 dev 主機組的成員 node2 …

openGauss學習筆記-43 openGauss 高級數據管理-事件觸發器

文章目錄 openGauss學習筆記-43 openGauss 高級數據管理-事件觸發器43.1 語法格式43.2 參數說明43.3 示例 openGauss學習筆記-43 openGauss 高級數據管理-事件觸發器 觸發器會在指定的ddl事件發生時自動執行函數。目前事件觸發器僅在PG兼容模式下可用。 43.1 語法格式 創建事…