并發(fā)包工具類:CyclicBarrier、CountDownLatch

場景:對賬系統(tǒng)最近越來越慢寿弱,老板讓優(yōu)化犯眠,用戶通過在線商城下單,會生成電子訂單症革,保存在訂單庫筐咧;之后物流會生成派送單給用戶發(fā)貨,派送單保存在派送單庫地沮。為了防止漏派送或者重復派送嗜浮,對賬系統(tǒng)每天還會校驗是否存在異常訂單羡亩。

image

對賬系統(tǒng)代碼抽象:

while(存在未對賬訂單){
  // 查詢未對賬訂單
  pos = getPOrders();
  // 查詢派送單
  dos = getDOrders();
  // 執(zhí)行對賬操作
  diff = check(pos, dos);
  // 差異寫入差異庫
  save(diff);
} 
思路:

單線程改成多線程并發(fā)執(zhí)行。
我們創(chuàng)建了兩個線程T1和T2危融,并行執(zhí)行查詢未對賬訂單getPOrders()和查詢派送單getDOrders()這兩個操作畏铆。在主線程中執(zhí)行對賬操作check()和差異寫入save()兩個操作。

主線程需要等待線程T1和T2執(zhí)行完才能執(zhí)行check()和save()這兩個操作吉殃,為此我們通過調(diào)用T1.join()和T2.join()來實現(xiàn)等待辞居,當T1和T2線程退出時,調(diào)用T1.join()和T2.join()的主線程就會從阻塞態(tài)被喚醒蛋勺,從而執(zhí)行之后的check()和save()瓦灶。

while(存在未對賬訂單){
  // 查詢未對賬訂單
  Thread T1 = new Thread(()->{
    pos = getPOrders();
  });
  T1.start();
  // 查詢派送單
  Thread T2 = new Thread(()->{
    dos = getDOrders();
  });
  T2.start();
  // 等待T1、T2結束
  T1.join();
  T2.join();
  // 執(zhí)行對賬操作
  diff = check(pos, dos);
  // 差異寫入差異庫
  save(diff);
} 
CountDownLatch實現(xiàn)線程等待

在while循環(huán)里面抱完,我們首先創(chuàng)建了一個CountDownLatch贼陶,計數(shù)器的初始值等于2,之后在pos = getPOrders();和dos = getDOrders();兩條語句的后面對計數(shù)器執(zhí)行減1操作巧娱,這個對計數(shù)器減1的操作是通過調(diào)用 latch.countDown(); 來實現(xiàn)的碉怔。在主線程中,我們通過調(diào)用 latch.await() 來實現(xiàn)對計數(shù)器等于0的等待禁添。

// 創(chuàng)建2個線程的線程池
Executor executor = 
  Executors.newFixedThreadPool(2);
while(存在未對賬訂單){
  // 計數(shù)器初始化為2
  CountDownLatch latch = 
    new CountDownLatch(2);
  // 查詢未對賬訂單
  executor.execute(()-> {
    pos = getPOrders();
    latch.countDown();
  });
  // 查詢派送單
  executor.execute(()-> {
    dos = getDOrders();
    latch.countDown();
  });
  
  // 等待兩個查詢操作結束
  latch.await();
  
  // 執(zhí)行對賬操作
  diff = check(pos, dos);
  // 差異寫入差異庫
  save(diff);
}
再次思考:

兩次查詢操作能夠和對賬操作并行撮胧,對賬操作還依賴查詢操作的結果,這明顯有點生產(chǎn)者-消費者的意思老翘,兩次查詢操作是生產(chǎn)者芹啥,對賬操作是消費者。既然是生產(chǎn)者-消費者模型铺峭,那就需要有個隊列墓怀,來保存生產(chǎn)者生產(chǎn)的數(shù)據(jù),而消費者則從這個隊列消費數(shù)據(jù)逛薇。

一個線程T1執(zhí)行訂單的查詢工作捺疼,一個線程T2執(zhí)行派送單的查詢工作,當線程T1和T2都各自生產(chǎn)完1條數(shù)據(jù)的時候永罚,通知線程T3執(zhí)行對賬操作啤呼。

隱藏條件,就是線程T1和線程T2的工作要步調(diào)一致呢袱,不能一個跑得太快官扣,一個跑得太慢,只有這樣才能做到各自生產(chǎn)完1條數(shù)據(jù)的時候羞福,通知線程T3惕蹄。

CyclicBarrier實現(xiàn)線程同步

創(chuàng)建了一個計數(shù)器初始值為2的CyclicBarrier,創(chuàng)建CyclicBarrier的時候,我們還傳入了一個回調(diào)函數(shù)卖陵,當計數(shù)器減到0的時候遭顶,會調(diào)用這個回調(diào)函數(shù)。

線程T1負責查詢訂單泪蔫,當查出一條時棒旗,調(diào)用 barrier.await() 來將計數(shù)器減1,同時等待計數(shù)器變成0撩荣;線程T2負責查詢派送單铣揉,當查出一條時,也調(diào)用 barrier.await() 來將計數(shù)器減1餐曹,同時等待計數(shù)器變成0逛拱;當T1和T2都調(diào)用 barrier.await() 的時候,計數(shù)器會減到0台猴,此時T1和T2就可以執(zhí)行下一條語句了朽合,同時會調(diào)用barrier的回調(diào)函數(shù)來執(zhí)行對賬操作。

CyclicBarrier的計數(shù)器有自動重置的功能卿吐,當減到0的時候旁舰,會自動重置你設置的初始值。

// 訂單隊列
Vector<P> pos;
// 派送單隊列
Vector<D> dos;
// 執(zhí)行回調(diào)的線程池 
Executor executor = 
  Executors.newFixedThreadPool(1);
final CyclicBarrier barrier =
  new CyclicBarrier(2, ()->{
    executor.execute(()->check());
  });
  
void check(){
  P p = pos.remove(0);
  D d = dos.remove(0);
  // 執(zhí)行對賬操作
  diff = check(p, d);
  // 差異寫入差異庫
  save(diff);
}
  
void checkAll(){
  // 循環(huán)查詢訂單庫
  Thread T1 = new Thread(()->{
    while(存在未對賬訂單){
      // 查詢訂單庫
      pos.add(getPOrders());
      // 等待
      barrier.await();
    }
  });
  T1.start();  
  // 循環(huán)查詢運單庫
  Thread T2 = new Thread(()->{
    while(存在未對賬訂單){
      // 查詢運單庫
      dos.add(getDOrders());
      // 等待
      barrier.await();
    }
  });
  T2.start();
}

總結:

CountDownLatch主要用來解決一個線程等待多個線程的場景嗡官,可以類比旅游團團長要等待所有的游客到齊才能去下一個景點;而CyclicBarrier是一組線程之間互相等待毯焕,更像是幾個驢友之間不離不棄衍腥。除此之外CountDownLatch的計數(shù)器是不能循環(huán)利用的,也就是說一旦計數(shù)器減到0纳猫,再有線程調(diào)用await()婆咸,該線程會直接通過。但CyclicBarrier的計數(shù)器是可以循環(huán)利用的芜辕,而且具備自動重置的功能尚骄,一旦計數(shù)器減到0會自動重置到你設置的初始值。除此之外侵续,CyclicBarrier還可以設置回調(diào)函數(shù)倔丈。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市状蜗,隨后出現(xiàn)的幾起案子需五,更是在濱河造成了極大的恐慌,老刑警劉巖轧坎,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宏邮,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機蜜氨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門械筛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人飒炎,你說我怎么就攤上這事埋哟。” “怎么了厌丑?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵定欧,是天一觀的道長。 經(jīng)常有香客問我怒竿,道長砍鸠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任耕驰,我火速辦了婚禮爷辱,結果婚禮上,老公的妹妹穿的比我還像新娘朦肘。我一直安慰自己饭弓,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布媒抠。 她就那樣靜靜地躺著弟断,像睡著了一般。 火紅的嫁衣襯著肌膚如雪趴生。 梳的紋絲不亂的頭發(fā)上阀趴,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音苍匆,去河邊找鬼刘急。 笑死,一個胖子當著我的面吹牛浸踩,可吹牛的內(nèi)容都是我干的叔汁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼检碗,長吁一口氣:“原來是場噩夢啊……” “哼据块!你這毒婦竟也來了?” 一聲冷哼從身側響起后裸,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瑰钮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后微驶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浪谴,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡开睡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了苟耻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篇恒。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凶杖,靈堂內(nèi)的尸體忽然破棺而出胁艰,到底是詐尸還是另有隱情,我是刑警寧澤智蝠,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布腾么,位于F島的核電站,受9級特大地震影響杈湾,放射性物質(zhì)發(fā)生泄漏解虱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一漆撞、第九天 我趴在偏房一處隱蔽的房頂上張望殴泰。 院中可真熱鬧,春花似錦浮驳、人聲如沸悍汛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽离咐。三九已至,卻和暖如春奉件,著一層夾襖步出監(jiān)牢的瞬間健霹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工瓶蚂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宣吱。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓窃这,卻偏偏與公主長得像,于是被迫代替她去往敵國和親征候。 傳聞我的和親對象是個殘疾皇子杭攻,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容