前文回顧
在《用好組合索引,性能提升10倍不止!》一文中,我們主要使用了CountDownLatch這個(gè)類來優(yōu)化程序的性能,在文末提出了一個(gè)思考題:其實(shí),上面的代碼不是允許的,你有更好的優(yōu)化方法嗎? 很多小伙伴的私信其實(shí)或多或少的說出了一些方案,但是沒說到真正的點(diǎn)子上。
這里,再向小伙伴們提出一個(gè)疑問:如果我們不使用CountDownLatch和CompletableFuture,讓你對(duì)前文的程序進(jìn)行優(yōu)化,你有思路嗎?
其實(shí)思路也很簡(jiǎn)單:蕞直接的方式就是創(chuàng)建一個(gè)計(jì)數(shù)器,將計(jì)數(shù)器的初始值設(shè)置為2,當(dāng)子線程1執(zhí)行完hasNoOrders = getHasNoOrders(); 這行代碼時(shí),將計(jì)數(shù)器的值減1,當(dāng)子線程2執(zhí)行完 hasNoStock = getHasNoStock(); 這行代碼時(shí),將計(jì)數(shù)器的值減1。在主線程中,等待計(jì)數(shù)器的值減為0,然后執(zhí)行后續(xù)的業(yè)務(wù)操作。
CountDownLatch類的總體思路也是這樣,小伙伴們可以根據(jù)這個(gè)思路自行實(shí)現(xiàn)程序性能的優(yōu)化,我就不再這里絮叨啦。
能否進(jìn)一步優(yōu)化?
我們先來看看之前程序的優(yōu)化效果圖。
通過仔細(xì)的分析,我們就會(huì)發(fā)現(xiàn):雖然getHasNoOrders()和getHasNoStock()這兩個(gè)方法實(shí)現(xiàn)了并行操作,但是getHasNoOrders()方法和getHasNoStock()方法和checkData()方法與saveCheckResult()方法之間還是串行的,如果能夠讓他們之間的操作并行化,那么系統(tǒng)的性能就可以得到進(jìn)一步提升了。如下圖所示。
如何實(shí)現(xiàn)上圖所示的優(yōu)化呢?接下來,我們先說說進(jìn)一步優(yōu)化的總體思路。
進(jìn)一步優(yōu)化思路
查詢未校對(duì)的訂單方法getHasNoOrders()和查詢未校對(duì)的庫(kù)存方法getHasNoStock()能夠并行執(zhí)行,校對(duì)數(shù)據(jù)的方法 checkData()還要依賴getHasNoOrders()方法和getHasNoStock()方法的結(jié)果,很明顯可以使用CompletableFuture來優(yōu)化,那除了CompletableFuture還有其他的方式嗎?今天,我們先不講CompletableFuture,先來看看其他的優(yōu)化方式。
大家認(rèn)真思考下,上述的場(chǎng)景中,一個(gè)方法的執(zhí)行需要等待另外兩個(gè)方法的執(zhí)行結(jié)果,是不是有點(diǎn)生產(chǎn)者-消費(fèi)者的意思呢?
有些小伙伴可能會(huì)說:這哪是生產(chǎn)者和消費(fèi)者模型啊?我們仔細(xì)想一下:兩次查詢未校對(duì)的數(shù)據(jù)就是生產(chǎn)者,校對(duì)數(shù)據(jù)的操作是消費(fèi)者。
我們可以使用隊(duì)列來保存生產(chǎn)者生產(chǎn)的數(shù)據(jù),而消費(fèi)者就從這個(gè)隊(duì)列中消費(fèi)數(shù)據(jù)。
由于查詢未校對(duì)的訂單方法getHasNoOrders()和查詢未校對(duì)的庫(kù)存方法getHasNoStock()是在兩個(gè)不同的線程中執(zhí)行的,這里,在具體實(shí)現(xiàn)時(shí),我們可以使用兩個(gè)隊(duì)列分別保存未校對(duì)的訂單數(shù)據(jù)和未校對(duì)的庫(kù)存數(shù)據(jù),校對(duì)數(shù)據(jù)的操作每次從隊(duì)列1中取出未校對(duì)的訂單數(shù)據(jù),從隊(duì)列2中取出未校對(duì)的庫(kù)存數(shù)據(jù),然后再執(zhí)行數(shù)據(jù)的校對(duì)操作。
接下來,我們?cè)偎伎家粋€(gè)問題:就是如何使用兩個(gè)隊(duì)列實(shí)現(xiàn)完全的并行化。
一個(gè)簡(jiǎn)單的方案就是在線程1中執(zhí)行查詢未校對(duì)訂單的數(shù)據(jù),在線程2中執(zhí)行查詢未校對(duì)庫(kù)存的數(shù)據(jù),當(dāng)線程1和線程2分別生產(chǎn)完一條數(shù)據(jù)時(shí),通知線程3執(zhí)行數(shù)據(jù)的校對(duì)操作。這里,有個(gè)關(guān)鍵的點(diǎn)就是線程1和線程2的執(zhí)行步調(diào)要一致,不能一個(gè)線程執(zhí)行的太快,一個(gè)線程執(zhí)行的太慢。
很顯然,線程1和線程2之間會(huì)存在相互等待的現(xiàn)象,說到這里,小伙伴們是不是就有解決方案啦?
我們先來說說優(yōu)化的總體思路吧: 首先,進(jìn)一步優(yōu)化存在兩個(gè)難點(diǎn):一個(gè)是線程1和線程2執(zhí)行的步調(diào)要一致,另外就是線程1和線程2中每次方法執(zhí)行完畢后,要通知線程3執(zhí)行數(shù)據(jù)校對(duì)操作。
我們也可以使用計(jì)數(shù)器的方式實(shí)現(xiàn),計(jì)數(shù)器的初始值為2,線程1執(zhí)行完getHasNoOrders()方法時(shí),對(duì)計(jì)數(shù)器減1,線程2執(zhí)行完getHasNoStock()方法時(shí),對(duì)計(jì)數(shù)器減1。如果計(jì)數(shù)器的值大于0時(shí),則線程1等待或者線程2等待。如果計(jì)數(shù)器的值等于0,則通知線程3執(zhí)行數(shù)據(jù)校對(duì)操作,并重新喚醒等待中的線程1或者線程2。同時(shí),需要我們將計(jì)數(shù)器的值重新設(shè)置為2,以此往復(fù)實(shí)現(xiàn)程序的優(yōu)化效果。
有小伙伴可能會(huì)說:這也太麻煩了吧!哈哈,自己實(shí)現(xiàn)確實(shí)挺麻煩的,不過Java并發(fā)類庫(kù)中為我們準(zhǔn)備好了一個(gè)實(shí)現(xiàn)上述場(chǎng)景的類——沒錯(cuò),可以使用Java并發(fā)類庫(kù)中的 CyclicBarrier 類實(shí)現(xiàn)。
使用CyclicBarrier進(jìn)一步優(yōu)化
使用CyclicBarrier進(jìn)一步優(yōu)化的具體方案就是:首先創(chuàng)建一個(gè)計(jì)數(shù)器初始值為2的CyclicBarrier對(duì)象,在構(gòu)造方法中傳入一個(gè)回調(diào)函數(shù),在回調(diào)函數(shù)中執(zhí)行數(shù)據(jù)的校對(duì)操作,當(dāng)計(jì)數(shù)器的值減為0時(shí),就會(huì)執(zhí)行這個(gè)回調(diào)函數(shù)。
在線程1中執(zhí)行完getHasNoOrders()方法并將結(jié)果放入隊(duì)列1后,執(zhí)行barrier.await()將計(jì)數(shù)器減1,同時(shí)等待計(jì)數(shù)器的值減為0。在線程2中執(zhí)行完getHasNoStock()方法并將結(jié)果放入隊(duì)列2后,執(zhí)行barrier.await()將計(jì)數(shù)器減1,同時(shí)等待計(jì)數(shù)器的值減為0。
當(dāng)計(jì)數(shù)器的值減為0時(shí),線程1和線程2繼續(xù)向下執(zhí)行,同時(shí)會(huì)調(diào)用回調(diào)函數(shù)來執(zhí)行數(shù)據(jù)的校對(duì)操作。
不僅如此,CyclicBarrier類還能夠自動(dòng)重置計(jì)數(shù)器的值,當(dāng)計(jì)數(shù)器的值減為0時(shí),它又會(huì)被自動(dòng)重置為初始值,這個(gè)功能使用起來也很方便。
接下來,我們看一下使用CyclicBarrier類優(yōu)化后的核心偽代碼,如下所示。
`// 訂單隊(duì)列
Vector<Order> orderQueue;
// 庫(kù)存隊(duì)列
Vector<Stock> stockQueue;
//創(chuàng)建查詢未校對(duì)訂單和未校對(duì)庫(kù)存的線程池
Executor executor = Executors.newFixedThreadPool(2);
//執(zhí)行數(shù)據(jù)校對(duì)的線程池
Executor checkExecutor = Executors.newFixedThreadPool(1);
final CyclicBarrier barrier =
new CyclicBarrier(2, ()->{
executor.execute(() -> checkDataAndSaveResult());
});
void checkDataAndSaveResult(){
Order o = orderQueue.remove(0);
Stock s = stockQueue.remove(0);
//校對(duì)數(shù)據(jù)并返回結(jié)果
checkResult = checkData(o, a);
//將結(jié)果信息保存到數(shù)據(jù)校對(duì)信息表中
saveCheckResult(checkResult);
}
void checkAllOrdersAndStock(){
//檢測(cè)是否存在未對(duì)賬訂單
checkOrders = checkOrders();
while(checkOrders != null){
executor.execute(()->{
//查詢未校對(duì)的訂單信息
hasNoOrders = getHasNoOrders();
orderQueue.add(hasNoOrders);
barrier.await();
});
executor.execute(()->{
//查詢未校對(duì)的庫(kù)存記錄
hasNoStock = getHasNoStock();
stockQueue.add(hasNoStock);
barrier.await();
});
}
}
`
至此,整個(gè)程序的優(yōu)化操作就完成了。
總結(jié)
在整個(gè)程序的優(yōu)化過程中,我們開始使用了CountDownLatch優(yōu)化程序,后面又使用了CyclicBarrier優(yōu)化程序。它兩個(gè)的區(qū)別就是:
好了,今天就到這兒吧,我們下期見~~