史廣
摘要:隨著Java并發(fā)(concurrem)工具包的推出,并發(fā)程序的開發(fā)方式得到了極大的優(yōu)化。較之以往的多線程設(shè)計(jì)機(jī)制,論文從四個(gè)方面深入探討了并發(fā)(conCurrent)工具包是如何提升并發(fā)編程效能的。
關(guān)鍵詞:并發(fā);多線程;java
doi:10.16083/j.cnki.1671-1580.2016.08.025
中圖分類號(hào):TP311
文獻(xiàn)標(biāo)識(shí)碼:A
文章編號(hào):1671-1580(2016)08-0078-04
在JDKl.5出現(xiàn)之后,Sun推出了并發(fā)(concur-rent)工具包以簡(jiǎn)化并發(fā)編程,它為開發(fā)者提供了更為實(shí)用的并發(fā)程序模型,使得編寫高效、易維護(hù)、結(jié)構(gòu)清晰的Java多線程程序更為容易。
一、簡(jiǎn)化了線程的管理,提升了線程的執(zhí)行效能
在JDKl.5之前,使用newThread()方式定義線程,需要考慮線程的創(chuàng)建、結(jié)束和結(jié)果的獲取等諸多細(xì)節(jié)。尤其在需要很多線程時(shí),線程的管理就會(huì)變得比較困難。不僅如此,使用newThread()方式定義線程,在時(shí)間和空間效率方面都存在不足。比如,定義好的線程不能重復(fù)利用;每次使用線程都需要向系統(tǒng)申請(qǐng)資源重新創(chuàng)建。另外,線程的創(chuàng)建和啟動(dòng)都需要一定的時(shí)間,這也會(huì)影響程序執(zhí)行效率。
線程池(ThreadPool)是并發(fā)(Concurrent)工具包中引入的機(jī)制,它對(duì)以上問題進(jìn)行了很好的優(yōu)化。線程池通過一個(gè)緩存池的空間預(yù)先創(chuàng)建了一部分線程,在我們需要使用的時(shí)候就從里面直接將線程資源取出來(lái)使用。在線程使用完畢之后,線程池可以將線程回收進(jìn)行重復(fù)利用。因此,在對(duì)性能要求較高或線程請(qǐng)求較多的情況下,線程池是一個(gè)很理想的選擇。
上述程序創(chuàng)建了一個(gè)可緩存線程池(cachedThread Pool):只需通過循環(huán)的方式,便可把想要完成的任務(wù)數(shù)量傳遞給線程池,線程池會(huì)創(chuàng)建盡可能多的必須線程來(lái)并行執(zhí)行。一旦前面的線程執(zhí)行結(jié)束后可以被重復(fù)使用。
除了可緩存線程池(cached Thread Pool),定長(zhǎng)線程池(Fix Thread Pool)可以創(chuàng)建固定數(shù)量的線程。在線程都被使用之后,后續(xù)申請(qǐng)使用的線程都會(huì)被阻塞。如果我們將上述代碼中的new.CachedThreadPool改為newFixedThreadPool(2),括號(hào)中的數(shù)字2,表示創(chuàng)建了兩個(gè)線程。下面的代碼不變,則相等于把5個(gè)任務(wù)交給兩個(gè)線程來(lái)完成。這就意味著,每一個(gè)線程完成一次任務(wù)后,并不會(huì)就此消逝,而是繼續(xù)完成剩下的任務(wù),直到所有任務(wù)完成。可見線程池確實(shí)簡(jiǎn)化了線程的管理,提升了線程的執(zhí)行效能
二、強(qiáng)化了對(duì)多線程并發(fā)的控制能力。簡(jiǎn)化了控制線程間協(xié)調(diào)合作的方法
在并發(fā)(concurrent)工具包中,包含了一些同步輔助類,使得開發(fā)者能夠更加輕松的對(duì)多線程進(jìn)行協(xié)調(diào)控制,豐富了多個(gè)線程間協(xié)作的方式。下面我們以閉鎖(countDownLatch)和信號(hào)量(sema-phore)為例進(jìn)行說(shuō)明。
1.閉鎖(countDownLatch)是一個(gè)并發(fā)構(gòu)造,它允許一個(gè)或多個(gè)線程等待一系列指定操作的完成。閉鎖(CountDownLatch)以一個(gè)給定的計(jì)數(shù)初始化,每調(diào)用一次countDown(),這一數(shù)量就減一,然后通過調(diào)用await()方法,將阻塞線程,使其等待直到計(jì)數(shù)到達(dá)零。
在主方法中,除t1和t2線程外,還有語(yǔ)句“Sys.tern.out.println(“HelloWorld”)”所在的主線程,三個(gè)線程本來(lái)沒有執(zhí)行的先后順序,但是如果運(yùn)行代碼,會(huì)發(fā)現(xiàn)主線程只在t1和t2完成后,才會(huì)執(zhí)行。這就是因?yàn)槲覀儗?duì)線程執(zhí)行的順序進(jìn)行了人工干預(yù),await()方法會(huì)一直阻塞主線程,直到countDown()方法將計(jì)數(shù)倒數(shù)至0。
2.信號(hào)量(Semaphore),有時(shí)被稱為信號(hào)燈,它負(fù)責(zé)協(xié)調(diào)各個(gè)線程,以保證它們能夠正確、合理的使用公共資源。信號(hào)量可以控制某個(gè)資源可被同時(shí)訪問的個(gè)數(shù),拿到信號(hào)量的線程可以進(jìn)入代碼,否則就等待,通過acquire()和release()獲取和釋放訪
從上面代碼中看出線程執(zhí)行的任務(wù)是連接數(shù)據(jù)庫(kù)。當(dāng)線程成功連接數(shù)據(jù)庫(kù)兩秒后,連接將自動(dòng)斷開。重要的是,由于服務(wù)器資源有限,在給定時(shí)間內(nèi),可以提供的連接數(shù)必須受到嚴(yán)格控制。如果此時(shí)有200個(gè)線程同時(shí)訪問數(shù)據(jù)庫(kù)資源,但服務(wù)器在同一時(shí)問內(nèi)只提供10個(gè)連接端口,這種情況就需要信號(hào)量來(lái)實(shí)現(xiàn)對(duì)線程的有效管控。
在上述代碼中,將信號(hào)量定義為10,每條線程在進(jìn)行數(shù)據(jù)庫(kù)連接時(shí),必須首先通過acquire()獲得信號(hào),并且在斷開數(shù)據(jù)庫(kù)連接后,通過release()釋放獲得的信號(hào),以便其他線程獲取。也就是說(shuō),雖然有200個(gè)線程同時(shí)想要進(jìn)行數(shù)據(jù)庫(kù)連接,但是在同一時(shí)間內(nèi),只能有10個(gè)線程獲得信號(hào)來(lái)并發(fā)執(zhí)行??梢钥闯?,信號(hào)量的應(yīng)用,豐富了我們對(duì)線程的管控方式,也簡(jiǎn)化了操作過程。
三、提供了多個(gè)具有線程安全性的類
在iaval.5出現(xiàn)之前,多線程訪問共享數(shù)據(jù)時(shí)造成的數(shù)據(jù)訪問沖突問題,往往需要耗費(fèi)程序員大量時(shí)間進(jìn)行調(diào)試規(guī)避。但是在并發(fā)(Concurrent)工具包中,提供的很多類本身就是線程安全的,這樣大大簡(jiǎn)化了并發(fā)編程的難度。比如前文中提到的閉鎖就是一個(gè)線程安全類。在閉鎖的示例代碼中,對(duì)象latch被兩條線程并發(fā)共享,且沒有synchronized關(guān)鍵字鎖定,但程序并不會(huì)出現(xiàn)訪問沖突。除Count.DownLatch以外,阻塞隊(duì)列(BlockingQueue)的線程安全性也為開發(fā)并發(fā)程序帶來(lái)了極大方便。
在經(jīng)典的“生產(chǎn)者”(producer)和“消費(fèi)者”(con-sumer)模型中,通過隊(duì)列可以很便利地實(shí)現(xiàn)兩者之問的數(shù)據(jù)共享。但是,在生產(chǎn)者和消費(fèi)者數(shù)據(jù)處理速度不匹配,且生產(chǎn)者產(chǎn)出數(shù)據(jù)的速度遠(yuǎn)大于消費(fèi)者消費(fèi)速度的情況下,生產(chǎn)者必須在數(shù)據(jù)累積到一定程度時(shí)暫停下來(lái)(阻塞生產(chǎn)者線程),以便消費(fèi)者線程把累積的數(shù)據(jù)處理完畢。然而,在并發(fā)(concur-rent)工具包發(fā)布以前,開發(fā)者不僅需要考慮所有上述細(xì)節(jié),還要兼顧效率和線程安全,開發(fā)的復(fù)雜度可見一斑。阻塞隊(duì)列很好地解決了如何在“生產(chǎn)者”(producer)和“消費(fèi)者”(consumer)之間高效安全“傳輸”數(shù)據(jù)的問題。阻塞隊(duì)列是一個(gè)高效并且線程安全的隊(duì)列類,它為我們快速搭建高質(zhì)量的多線程程序帶來(lái)極大的便利。
上面是一個(gè)典型的“生產(chǎn)者”(producer)和“消費(fèi)者”(consumer)模型,生產(chǎn)者和消費(fèi)者共享阻塞隊(duì)列queue。生產(chǎn)者的生產(chǎn)速度遠(yuǎn)大于消費(fèi)者,因?yàn)橄M(fèi)者每取出一個(gè)數(shù)據(jù)需要等待100毫秒。幸運(yùn)的是,阻塞隊(duì)列首先是一個(gè)線程安全隊(duì)列,其次,當(dāng)生產(chǎn)者將隊(duì)列長(zhǎng)度填充到10時(shí),put()方法會(huì)進(jìn)入等待狀態(tài),同理,當(dāng)隊(duì)列長(zhǎng)度縮減到0時(shí)take()也會(huì)進(jìn)行等待。這就使得使用阻塞隊(duì)列開發(fā)多線程常見模型的復(fù)雜度大大降低。
四、針對(duì)并發(fā)程序可能引起的死鎖問題給出了更為便捷的解決方案
當(dāng)線程需要同時(shí)獲得多個(gè)互斥鎖才可運(yùn)行時(shí),如果有多條線程并行且獲取互斥鎖的順序不同,就可能引發(fā)死鎖(DeadLock)現(xiàn)象。
在并發(fā)(concurrent)工具包中,引入了重入鎖(Re-entrantlock)。重入鎖的基本原理和synchronized語(yǔ)句塊相似,都是通過加互斥鎖的方式限定線程的行為,它們的第一個(gè)不同點(diǎn)在于,首先當(dāng)需要加一個(gè)以上的互斥鎖時(shí),使用Reentrant lock避免了像synchronized語(yǔ)句塊一樣的嵌套。更為重要的是,re-entrant lock有tryLock()功能,它使得線程在需要同時(shí)獲得多個(gè)互斥鎖的情況下,具備了更加靈活的應(yīng)對(duì)機(jī)制。
上述代碼中,acquireLocks()方法的作用就是幫助線程分別獲得兩個(gè)互斥鎖,當(dāng)只獲得一個(gè),另一個(gè)獲取失敗的情況下,由于tryLock()方法的作用,使得程序可以在此情況下,放棄已經(jīng)獲得的互斥鎖,以便其它線程同時(shí)獲取。這也就很巧妙的避免了死鎖現(xiàn)象的出現(xiàn)。
五、結(jié)語(yǔ)
并發(fā)(concurrent)工具包的推出,使得原來(lái)很麻煩的并發(fā)處理得以輕松完成,它實(shí)現(xiàn)了很多{avathread原生API很費(fèi)時(shí)才能實(shí)現(xiàn)的功能。并發(fā)(Con.current)工具包的優(yōu)點(diǎn)可以概括為:簡(jiǎn)化了線程的管理,提升了線程的執(zhí)行效能;強(qiáng)化了對(duì)多線程并發(fā)的控制能力,簡(jiǎn)化了控制線程問協(xié)調(diào)合作的方法;提供了多個(gè)具有線程安全性的類;針對(duì)并發(fā)程序可能引起的死鎖等問題給出了更為便捷的解決方案。當(dāng)然,隨著iava語(yǔ)言的發(fā)展,我們相信新的工具包還會(huì)不斷更新,來(lái)滿足日益繁雜的開發(fā)需求。