常見(jiàn)的并發(fā)場(chǎng)景
線程池
并發(fā)最常見(jiàn)用于線程池,顯然使用線程池可以有效的提高吞吐量。
最常見(jiàn)、比較復(fù)雜一個(gè)場(chǎng)景是Web容器的線程池。Web容器使用線程池同步或者異步處理HTTP請(qǐng)求,同時(shí)這也可以有效的復(fù)用HTTP連接,降低資源申請(qǐng)的開(kāi)銷。通常我們認(rèn)為HTTP請(qǐng)求時(shí)非常昂貴的,并且也是比較耗費(fèi)資源和性能的,所以線程池在這里就扮演了非常重要的角色。
在線程池的章節(jié)中非常詳細(xì)的討論了線程池的原理和使用,同時(shí)也提到了,線程池的配置和參數(shù)對(duì)性能的影響是巨大的。不盡如此,受限于資源(機(jī)器的性能、網(wǎng)絡(luò)的帶寬等等)、依賴的服務(wù),客戶端的響應(yīng)速度等,線程池的威力也不會(huì)一直增長(zhǎng)。達(dá)到了線程池的瓶頸后,性能和吞吐量都會(huì)大幅度降低。
一直增加機(jī)器的性能或者增大線程的個(gè)數(shù),并不一定能有效的提高吞吐量。高并發(fā)的情況下,機(jī)器的負(fù)載會(huì)大幅提升,這時(shí)候機(jī)器的穩(wěn)定性、服務(wù)的可靠性都會(huì)下降。
盡管如此,線程池依然是提高吞吐量的一個(gè)有效措施,配合合適的參數(shù)能夠有效的充分利用資源,提高資源的利用率。
任務(wù)隊(duì)列
除了線程池是比較發(fā)雜的并發(fā)場(chǎng)景外,任務(wù)隊(duì)列也是一個(gè)不錯(cuò)的并發(fā)工具。JDK內(nèi)部有大量的隊(duì)列(Queue),這些工具不僅能夠方便使用,提高生產(chǎn)力,也能夠進(jìn)行組合適應(yīng)于不同的場(chǎng)景。即使線程池內(nèi)部,也是用了任務(wù)隊(duì)列來(lái)處理任務(wù)的積壓,平衡資源的消耗。
安全的任務(wù)隊(duì)列能夠有效的平衡機(jī)器的復(fù)雜,抵消由于峰值和波動(dòng)帶來(lái)的不穩(wěn)定,有效提高服務(wù)的可靠性。同時(shí)任務(wù)隊(duì)列的處理也有助于統(tǒng)計(jì)和分析服務(wù)的狀況。
任務(wù)隊(duì)列也可以在多個(gè)線程之間傳遞數(shù)據(jù),有助于并行處理任務(wù)。例如經(jīng)典的“生產(chǎn)者-消費(fèi)者”模型就可以有效的提高多個(gè)線程的并行處理能力。在IO延時(shí)比較大的服務(wù)中尤其有效。 我最喜歡的一個(gè)案例是導(dǎo)數(shù)據(jù)是,一個(gè)線程負(fù)責(zé)往固定大小的任務(wù)隊(duì)列中壓入大量的數(shù)據(jù),隊(duì)列滿了以后就暫停,另外幾個(gè)線程負(fù)責(zé)從任務(wù)隊(duì)列中獲取數(shù)據(jù)并消費(fèi)。這將串行的“生產(chǎn)-消費(fèi)”,變成了并行的“生產(chǎn)-消費(fèi)”。實(shí)踐證明極大的節(jié)省任務(wù)處理時(shí)間。
異步處理
線程池也是異步處理的一種表現(xiàn)形式,除此之外,使用異步處理的目的也是為了提高服務(wù)的處理速度。 例如AOP的一個(gè)例子就是使用切面來(lái)記錄日志,如果說(shuō)我們要遠(yuǎn)程收集日志,顯然不希望由于收集日志而影響服務(wù)本身。這時(shí)候就將日志收集的過(guò)程進(jìn)行異步處理。
如今大量的開(kāi)源組件都喜歡使用異步處理來(lái)提高IO的效率,某些不需要同步返回的操作使用異步處理后能夠有效的提高吞吐量。
當(dāng)然,異步也不總是令人滿意的,也會(huì)有相應(yīng)的問(wèn)題。例如引入異步設(shè)計(jì)后的復(fù)雜性,線程中斷后的處理機(jī)制,失敗后的處理策略,產(chǎn)生的消息比消費(fèi)的還快時(shí)怎么辦,關(guān)閉程序時(shí)如何關(guān)閉異步處理邏輯等等。這都會(huì)增加系統(tǒng)的復(fù)雜性。
盡管大量的服務(wù)、業(yè)務(wù)使用異步來(lái)處理,但是很顯然需要有保障機(jī)制能夠保證異步處理的邏輯正確性。如果認(rèn)為異步處理的任務(wù)不是特別重要,或者說(shuō)主業(yè)務(wù)不能因?yàn)楦綄贅I(yè)務(wù)的邏輯出錯(cuò)而崩潰,那么使用異步處理是正確的選擇。
同步操作
并發(fā)操作的同時(shí)還需要維護(hù)數(shù)據(jù)的一致性,或多或少的會(huì)涉及到同步操作。正確的使用原子操作,合理的使用獨(dú)占鎖和讀寫鎖也是一個(gè)很大的挑戰(zhàn)。
線程間的協(xié)調(diào)與通信,尤其是狀態(tài)的同步都是比較困難的。我們看到線程池ThreadPoolExecutor的實(shí)現(xiàn)為了解決各個(gè)線程的執(zhí)行狀態(tài),引入的很多的同步操作。線程越來(lái)越多的情況下,同步的成本會(huì)越來(lái)越高,同時(shí)也有可能引入死鎖的情況。
盡管如此,單個(gè)JVM內(nèi)部的多線程同步還是比較容易控制的。JDK內(nèi)部也提供了大量的工具來(lái)方便完成數(shù)據(jù)的同步。例如Lock/Condition/CountDownLatch/CyclicBarrier/Semaphore/Exchanger等等。
分布式鎖
分布式的并發(fā)問(wèn)題更難以處理,根據(jù)CAP的原理,基本上沒(méi)有一個(gè)至善至美的方案。 分布式資源協(xié)調(diào)使用分布式鎖是一個(gè)不錯(cuò)的選擇。Google的分布式鎖(建立在BigTable之上),Zookeeper的分布式鎖,甚至簡(jiǎn)單的利用memcache的add操作或者redis的setnx操作建立偽分布式鎖也可以解決類似的問(wèn)題。