精通Java并發(fā)編程(第2版)_第1頁(yè)
精通Java并發(fā)編程(第2版)_第2頁(yè)
精通Java并發(fā)編程(第2版)_第3頁(yè)
精通Java并發(fā)編程(第2版)_第4頁(yè)
精通Java并發(fā)編程(第2版)_第5頁(yè)
已閱讀5頁(yè),還剩335頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

\h精通Java并發(fā)編程(第2版)目錄\h第1章第一步:并發(fā)設(shè)計(jì)原理\h1.1基本的并發(fā)概念\h1.1.1并發(fā)與并行\(zhòng)h1.1.2同步\h1.1.3不可變對(duì)象\h1.1.4原子操作和原子變量\h1.1.5共享內(nèi)存與消息傳遞\h1.2并發(fā)應(yīng)用程序中可能出現(xiàn)的問(wèn)題\h1.2.1數(shù)據(jù)競(jìng)爭(zhēng)\h1.2.2死鎖\h1.2.3活鎖\h1.2.4資源不足\h1.2.5優(yōu)先權(quán)反轉(zhuǎn)\h1.3設(shè)計(jì)并發(fā)算法的方法論\h1.3.1起點(diǎn):算法的一個(gè)串行版本\h1.3.2第1步:分析\h1.3.3第2步:設(shè)計(jì)\h1.3.4第3步:實(shí)現(xiàn)\h1.3.5第4步:測(cè)試\h1.3.6第5步:調(diào)整\h1.3.7結(jié)論\h1.4Java并發(fā)API\h1.4.1基本并發(fā)類\h1.4.2同步機(jī)制\h1.4.3執(zhí)行器\h1.4.4Fork/Join框架\h1.4.5并行流\h1.4.6并發(fā)數(shù)據(jù)結(jié)構(gòu)\h1.5并發(fā)設(shè)計(jì)模式\h1.5.1信號(hào)模式\h1.5.2會(huì)合模式\h1.5.3互斥模式\h1.5.4多元復(fù)用模式\h1.5.5柵欄模式\h1.5.6雙重檢查鎖定模式\h1.5.7讀-寫(xiě)鎖模式\h1.5.8線程池模式\h1.5.9線程局部存儲(chǔ)模式\h1.6設(shè)計(jì)并發(fā)算法的提示和技巧\h1.6.1正確識(shí)別獨(dú)立任務(wù)\h1.6.2在盡可能高的層面上實(shí)施并發(fā)處理\h1.6.3考慮伸縮性\h1.6.4使用線程安全API\h1.6.5絕不要假定執(zhí)行順序\h1.6.6在靜態(tài)和共享場(chǎng)合盡可能使用局部線程變量\h1.6.7尋找更易于并行處理的算法版本\h1.6.8盡可能使用不可變對(duì)象\h1.6.9通過(guò)對(duì)鎖排序來(lái)避免死鎖\h1.6.10使用原子變量代替同步\h1.6.11占有鎖的時(shí)間盡可能短\h1.6.12謹(jǐn)慎使用延遲初始化\h1.6.13避免在臨界段中使用阻塞操作\h1.7小結(jié)\h第2章使用基本元素:Thread和Runnable\h2.1Java中的線程\h2.1.1Java中的線程:特征和狀態(tài)\h2.1.2Thread類和Runnable接口\h2.2第一個(gè)例子:矩陣乘法\h2.2.1公共類\h2.2.2串行版本\h2.2.3并行版本\h2.3第二個(gè)例子:文件搜索\h2.3.1公共類\h2.3.2串行版本\h2.3.3并發(fā)版本\h2.3.4對(duì)比解決方案\h2.4小結(jié)\h第3章管理大量線程:執(zhí)行器\h3.1執(zhí)行器簡(jiǎn)介\h3.1.1執(zhí)行器的基本特征\h3.1.2執(zhí)行器框架的基本組件\h3.2第一個(gè)例子:k-最近鄰算法\h3.2.1k-最近鄰算法:串行版本\h3.2.2k-最近鄰算法:細(xì)粒度并發(fā)版本\h3.2.3k-最近鄰算法:粗粒度并發(fā)版本\h3.2.4對(duì)比解決方案\h3.3第二個(gè)例子:客戶端/服務(wù)器環(huán)境下的并發(fā)處理\h3.3.1客戶端/服務(wù)器:串行版\h3.3.2客戶端/服務(wù)器:并行版本\h3.3.3額外的并發(fā)服務(wù)器組件\h3.3.4對(duì)比兩種解決方案\h3.3.5其他重要方法\h3.4小結(jié)\h第4章充分利用執(zhí)行器\h4.1執(zhí)行器的高級(jí)特性\h4.1.1任務(wù)的撤銷\h4.1.2任務(wù)執(zhí)行調(diào)度\h4.1.3重載執(zhí)行器方法\h4.1.4更改一些初始化參數(shù)\h4.2第一個(gè)例子:高級(jí)服務(wù)器應(yīng)用程序\h4.2.1ServerExecutor類\h4.2.2命令類\h4.2.3服務(wù)器部件\h4.2.4客戶端部件\h4.3第二個(gè)例子:執(zhí)行周期性任務(wù)\h4.3.1公共部件\h4.3.2基礎(chǔ)閱讀器\h4.3.3高級(jí)閱讀器\h4.4有關(guān)執(zhí)行器的其他信息\h4.5小結(jié)\h第5章從任務(wù)獲取數(shù)據(jù):Callable接口與Future接口\h5.1Callable接口和Future接口簡(jiǎn)介\h5.1.1Callable接口\h5.1.2Future接口\h5.2第一個(gè)例子:?jiǎn)卧~最佳匹配算法\h5.2.1公共類\h5.2.2最佳匹配算法:串行版本\h5.2.3最佳匹配算法:第一個(gè)并發(fā)版本\h5.2.4最佳匹配算法:第二個(gè)并發(fā)版本\h5.2.5單詞存在算法:串行版本\h5.2.6單詞存在算法:并行版本\h5.2.7對(duì)比解決方案\h5.3第二個(gè)例子:為文檔集創(chuàng)建倒排索引\h5.3.1公共類\h5.3.2串行版本\h5.3.3第一個(gè)并發(fā)版本:每個(gè)文檔一個(gè)任務(wù)\h5.3.4第二個(gè)并發(fā)版本:每個(gè)任務(wù)多個(gè)文檔\h5.3.5對(duì)比解決方案\h5.3.6其他相關(guān)方法\h5.4小結(jié)\h第6章運(yùn)行分為多階段的任務(wù):Phaser類\h6.1Phaser類簡(jiǎn)介\h6.1.1參與者的注冊(cè)與注銷\h6.1.2同步階段變更\h6.1.3其他功能\h6.2第一個(gè)例子:關(guān)鍵字抽取算法\h6.2.1公共類\h6.2.2串行版本\h6.2.3并發(fā)版本\h6.2.4對(duì)比兩種解決方案\h6.3第二個(gè)例子:遺傳算法\h6.3.1公共類\h6.3.2串行版本\h6.3.3并發(fā)版本\h6.3.4對(duì)比兩種解決方案\h6.4小結(jié)\h第7章優(yōu)化分治解決方案:Fork/Join框架\h7.1Fork/Join框架簡(jiǎn)介\h7.1.1Fork/Join框架的基本特征\h7.1.2Fork/Join框架的局限性\h7.1.3Fork/Join框架的組件\h7.2第一個(gè)例子:k-means聚類算法\h7.2.1公共類\h7.2.2串行版本\h7.2.3并發(fā)版本\h7.2.4對(duì)比解決方案\h7.3第二個(gè)例子:數(shù)據(jù)篩選算法\h7.3.1公共特性\h7.3.2串行版\h7.3.3并發(fā)版本\h7.3.4對(duì)比兩個(gè)版本\h7.4第三個(gè)例子:歸并排序算法\h7.4.1共享類\h7.4.2串行版本\h7.4.3并發(fā)版本\h7.4.4對(duì)比兩個(gè)版本\h7.5Fork/Join框架的其他方法\h7.6小結(jié)\h第8章使用并行流處理大規(guī)模數(shù)據(jù)集:MapReduce模型\h8.1流的簡(jiǎn)介\h8.1.1流的基本特征\h8.1.2流的組成部分\h8.1.3MapReduce與MapCollect\h8.2第一個(gè)例子:數(shù)值綜合分析應(yīng)用程序\h8.2.1并發(fā)版本\h8.2.2串行版本\h8.2.3對(duì)比兩個(gè)版本\h8.3第二個(gè)例子:信息檢索工具\(yùn)h8.3.1約簡(jiǎn)操作簡(jiǎn)介\h8.3.2第一種方式:全文檔查詢\h8.3.3第二種方式:約簡(jiǎn)的文檔查詢\h8.3.4第三種方式:生成一個(gè)含有結(jié)果的HTML文件\h8.3.5第四種方式:預(yù)先載入倒排索引\h8.3.6第五種方式:使用我們的執(zhí)行器\h8.3.7從倒排索引獲取數(shù)據(jù):ConcurrentData類\h8.3.8獲取文件中的單詞數(shù)\h8.3.9獲取文件的平均tfxidf值\h8.3.10獲取索引中的最大tfxidf值和最小tfxidf值\h8.3.11ConcurrentMain類\h8.3.12串行版\h8.3.13對(duì)比兩種解決方案\h8.4小結(jié)\h第9章使用并行流處理大規(guī)模數(shù)據(jù)集:MapCollect模型\h9.1使用流收集數(shù)據(jù)\hcollect()方法\h9.2第一個(gè)例子:無(wú)索引條件下的數(shù)據(jù)搜索\h9.2.1基本類\h9.2.2第一種方式:基本搜索\h9.2.3第二種方式:高級(jí)搜索\h9.2.4本例的串行實(shí)現(xiàn)\h9.2.5對(duì)比實(shí)現(xiàn)方案\h9.3第二個(gè)例子:推薦系統(tǒng)\h9.3.1公共類\h9.3.2推薦系統(tǒng):主類\h9.3.3ConcurrentLoaderAccumulator類\h9.3.4串行版\h9.3.5對(duì)比兩個(gè)版本\h9.4第三個(gè)例子:社交網(wǎng)絡(luò)中的共同聯(lián)系人\h9.4.1基本類\h9.4.2并發(fā)版本\h9.4.3串行版本\h9.4.4對(duì)比兩個(gè)版本\h9.5小結(jié)\h第10章異步流處理:反應(yīng)流\h10.1Java反應(yīng)流簡(jiǎn)介\h10.1.1Flow.Publisher接口\h10.1.2Flow.Subscriber接口\h10.1.3Flow.Subscription接口\h10.1.4SubmissionPublisher類\h10.2第一個(gè)例子:面向事件通知的集中式系統(tǒng)\h10.2.1Event類\h10.2.2Producer類\h10.2.3Consumer類\h10.2.4Main類\h10.3第二個(gè)例子:新聞系統(tǒng)\h10.3.1News類\h10.3.2發(fā)布者相關(guān)的類\h10.3.3Consumer類\h10.3.4Main類\h10.4小結(jié)\h第11章探究并發(fā)數(shù)據(jù)結(jié)構(gòu)和同步工具\(yùn)h11.1并發(fā)數(shù)據(jù)結(jié)構(gòu)\h11.1.1阻塞型數(shù)據(jù)結(jié)構(gòu)和非阻塞型數(shù)據(jù)結(jié)構(gòu)\h11.1.2并發(fā)數(shù)據(jù)結(jié)構(gòu)\h11.1.3使用新特性\h11.1.4原子變量\h11.1.5變量句柄\h11.2同步機(jī)制\h11.2.1CommonTask類\h11.2.2Lock接口\h11.2.3Semaphore類\h11.2.4CountDownLatch類\h11.2.5CyclicBarrier類\h11.2.6CompletableFuture類\h11.3小結(jié)\h第12章測(cè)試與監(jiān)視并發(fā)應(yīng)用程序\h12.1監(jiān)視并發(fā)對(duì)象\h12.1.1監(jiān)視線程\h12.1.2監(jiān)視鎖\h12.1.3監(jiān)視執(zhí)行器\h12.1.4監(jiān)視Fork/Join框架\h12.1.5監(jiān)視Phaser\h12.1.6監(jiān)視流API\h12.2監(jiān)視并發(fā)應(yīng)用程序\h12.2.1Overview選項(xiàng)卡\h12.2.2Memory選項(xiàng)卡\h12.2.3Threads選項(xiàng)卡\h12.2.4Classes選項(xiàng)卡\h12.2.5VMSummary選項(xiàng)卡\h12.2.6MBeans選項(xiàng)卡\h12.2.7About選項(xiàng)卡\h12.3測(cè)試并發(fā)應(yīng)用程序\h12.3.1使用MultithreadedTC測(cè)試并發(fā)應(yīng)用程序\h12.3.2使用JavaPathfinder測(cè)試并發(fā)應(yīng)用程序\h12.4小結(jié)\h第13章JVM中的并發(fā)處理:Clojure、帶有GPars庫(kù)的Groovy以及Scala\h13.1Clojure的并發(fā)處理\h13.1.1使用Java元素\h13.1.2引用類型\h13.1.3Ref對(duì)象\h13.1.4Delay\h13.1.5Future\h13.1.6Promise\h13.2Groovy及其GPars庫(kù)的并發(fā)處理\h13.3軟件事務(wù)性內(nèi)存\h13.3.1使用Java元素\h13.3.2數(shù)據(jù)并行處理\h13.3.3Fork/Join處理\h13.3.4Actor\h13.3.5Agent\h13.3.6Dataflow\h13.4Scala的并發(fā)處理\h13.4.1Scala中的Future對(duì)象\h13.4.2Promise\h13.5小結(jié)注:原文檔電子版(非掃描),需要的請(qǐng)下載本文檔后留言謝謝。第1章第一步:并發(fā)設(shè)計(jì)原理計(jì)算機(jī)系統(tǒng)的用戶總是希望自己的系統(tǒng)具有更好的性能。他們想要獲得質(zhì)量更高的視頻、更好的視頻游戲和更快的網(wǎng)絡(luò)速度。幾年前,提高處理器的速度可以為用戶提供更好的性能。但是如今,處理器的速度并沒(méi)有加快。取而代之的是,處理器增加了更多核心,這樣操作系統(tǒng)就可以同時(shí)執(zhí)行多個(gè)任務(wù)。這就是所謂的并發(fā)處理。并發(fā)編程涵蓋了在一臺(tái)計(jì)算機(jī)上同時(shí)運(yùn)行多個(gè)任務(wù)或進(jìn)程所需的所有工具和技術(shù),以及任務(wù)或進(jìn)程之間為消除數(shù)據(jù)丟失或不一致而進(jìn)行的通信和同步。本章將探討如下主題?;镜牟l(fā)概念。并發(fā)應(yīng)用程序中可能出現(xiàn)的問(wèn)題。設(shè)計(jì)并發(fā)算法的方法論。Java并發(fā)API。并發(fā)設(shè)計(jì)模式。設(shè)計(jì)并發(fā)算法的提示和技巧。1.1基本的并發(fā)概念首先介紹一下并發(fā)的基本概念。要理解本書(shū)其余的內(nèi)容,必須先理解這些概念。1.1.1并發(fā)與并行并發(fā)和并行是非常相似的概念,不同的作者會(huì)給這兩個(gè)概念下不同的定義。關(guān)于并發(fā),最被人們認(rèn)可的定義是,在單個(gè)處理器上采用單核執(zhí)行多個(gè)任務(wù)即為并發(fā)。在這種情況下,操作系統(tǒng)的任務(wù)調(diào)度程序會(huì)很快從一個(gè)任務(wù)切換到另一個(gè)任務(wù),因此看起來(lái)所有任務(wù)都是同時(shí)運(yùn)行的。對(duì)于并行來(lái)說(shuō)也有同樣的定義:同一時(shí)間在不同的計(jì)算機(jī)、處理器或處理器核心上同時(shí)運(yùn)行多個(gè)任務(wù),就是所謂的“并行”。另一個(gè)關(guān)于并發(fā)的定義是,在系統(tǒng)上同時(shí)運(yùn)行多個(gè)任務(wù)(不同的任務(wù))就是并發(fā)。而另一個(gè)關(guān)于并行的定義是:同時(shí)在某個(gè)數(shù)據(jù)集的不同部分之上運(yùn)行同一任務(wù)的不同實(shí)例就是并行。關(guān)于并行的最后一個(gè)定義是,系統(tǒng)中同時(shí)運(yùn)行了多個(gè)任務(wù)。關(guān)于并發(fā)的最后一個(gè)定義是,一種解釋程序員將任務(wù)和它們對(duì)共享資源的訪問(wèn)同步的不同技術(shù)和機(jī)制的方法。正如你看到的,這兩個(gè)概念非常相似,而且這種相似性隨著多核處理器的發(fā)展也在不斷增強(qiáng)。1.1.2同步在并發(fā)中,我們可以將同步定義為一種協(xié)調(diào)兩個(gè)或更多任務(wù)以獲得預(yù)期結(jié)果的機(jī)制。同步方式有兩種??刂仆剑豪?,當(dāng)一個(gè)任務(wù)的開(kāi)始依賴于另一個(gè)任務(wù)的結(jié)束時(shí),第二個(gè)任務(wù)不能在第一個(gè)任務(wù)完成之前開(kāi)始。數(shù)據(jù)訪問(wèn)同步:當(dāng)兩個(gè)或更多任務(wù)訪問(wèn)共享變量時(shí),在任意時(shí)間里,只有一個(gè)任務(wù)可以訪問(wèn)該變量。與同步密切相關(guān)的一個(gè)概念是臨界段。臨界段是一段代碼,由于它可以訪問(wèn)共享資源,因此在任何給定時(shí)間內(nèi),只能夠被一個(gè)任務(wù)執(zhí)行?;コ馐怯脕?lái)保證這一要求的機(jī)制,而且可以采用不同的方式來(lái)實(shí)現(xiàn)。請(qǐng)記住,同步可以幫助你在完成并發(fā)任務(wù)的同時(shí)避免一些錯(cuò)誤(本章稍后將詳述),但是它也為你的算法引入了一些開(kāi)銷。你必須非常仔細(xì)地計(jì)算任務(wù)的數(shù)量,這些任務(wù)可以獨(dú)立執(zhí)行,而無(wú)須并行算法中的互通信。這就涉及并發(fā)算法的粒度。如果算法有著粗粒度(低互通信的大型任務(wù)),同步方面的開(kāi)銷就會(huì)較低。然而,也許你不會(huì)用到系統(tǒng)所有的核心。如果算法有著細(xì)粒度(高互通信的小型任務(wù)),同步方面的開(kāi)銷就會(huì)很高,而且該算法的吞吐量可能不會(huì)很好。并發(fā)系統(tǒng)中有不同的同步機(jī)制。從理論角度來(lái)看,最流行的機(jī)制如下。信號(hào)量(semaphore):一種用于控制對(duì)一個(gè)或多個(gè)單位資源進(jìn)行訪問(wèn)的機(jī)制。它有一個(gè)用于存放可用資源數(shù)量的變量,并且可以采用兩種原子操作來(lái)管理該變量的值。互斥(mutex,mutualexclusion的簡(jiǎn)寫(xiě)形式)是一種特殊類型的信號(hào)量,它只能取兩個(gè)值(即資源空閑和資源忙),而且只有將互斥設(shè)置為忙的那個(gè)進(jìn)程才可以釋放它。互斥可以通過(guò)保護(hù)臨界段來(lái)幫助你避免出現(xiàn)競(jìng)爭(zhēng)條件。監(jiān)視器:一種在共享資源之上實(shí)現(xiàn)互斥的機(jī)制。它有一個(gè)互斥、一個(gè)條件變量、兩種操作(等待條件和通報(bào)條件)。一旦你通報(bào)了該條件,在等待它的任務(wù)中只有一個(gè)會(huì)繼續(xù)執(zhí)行。在本章中,你將要學(xué)習(xí)的與同步相關(guān)的最后一個(gè)概念是線程安全。如果共享數(shù)據(jù)的所有用戶都受到同步機(jī)制的保護(hù),那么代碼(或方法、對(duì)象)就是線程安全的。數(shù)據(jù)的非阻塞的CAS(compare-and-swap,比較和交換)原語(yǔ)是不可變的,這樣就可以在并發(fā)應(yīng)用程序中使用該代碼而不會(huì)出任何問(wèn)題。1.1.3不可變對(duì)象不可變對(duì)象是一種非常特殊的對(duì)象。在其初始化后,不能修改其可視狀態(tài)(其屬性值)。如果想修改一個(gè)不可變對(duì)象,那么你就必須創(chuàng)建一個(gè)新的對(duì)象。不可變對(duì)象的主要優(yōu)點(diǎn)在于它是線程安全的。你可以在并發(fā)應(yīng)用程序中使用它而不會(huì)出現(xiàn)任何問(wèn)題。不可變對(duì)象的一個(gè)例子就是Java中的String類。當(dāng)你給一個(gè)String對(duì)象賦新值時(shí),會(huì)創(chuàng)建一個(gè)新的String對(duì)象。1.1.4原子操作和原子變量與應(yīng)用程序的其他任務(wù)相比,原子操作是一種發(fā)生在瞬間的操作。在并發(fā)應(yīng)用程序中,可以通過(guò)一個(gè)臨界段來(lái)實(shí)現(xiàn)原子操作,以便對(duì)整個(gè)操作采用同步機(jī)制。原子變量是一種通過(guò)原子操作來(lái)設(shè)置和獲取其值的變量??梢允褂媚撤N同步機(jī)制來(lái)實(shí)現(xiàn)一個(gè)原子變量,或者也可以使用CAS以無(wú)鎖方式來(lái)實(shí)現(xiàn)一個(gè)原子變量,而這種方式并不需要任何同步機(jī)制。1.1.5共享內(nèi)存與消息傳遞任務(wù)可以通過(guò)兩種不同的方法來(lái)相互通信。第一種方法是共享內(nèi)存,通常用于在同一臺(tái)計(jì)算機(jī)上運(yùn)行多任務(wù)的情況。任務(wù)在讀取和寫(xiě)入值的時(shí)候使用相同的內(nèi)存區(qū)域。為了避免出現(xiàn)問(wèn)題,對(duì)該共享內(nèi)存的訪問(wèn)必須在一個(gè)由同步機(jī)制保護(hù)的臨界段內(nèi)完成。另一種同步機(jī)制是消息傳遞,通常用于在不同計(jì)算機(jī)上運(yùn)行多任務(wù)的情形。當(dāng)一個(gè)任務(wù)需要與另一個(gè)任務(wù)通信時(shí),它會(huì)發(fā)送一個(gè)遵循預(yù)定義協(xié)議的消息。如果發(fā)送方保持阻塞并等待響應(yīng),那么該通信就是同步的;如果發(fā)送方在發(fā)送消息后繼續(xù)執(zhí)行自己的流程,那么該通信就是異步的。1.2并發(fā)應(yīng)用程序中可能出現(xiàn)的問(wèn)題編寫(xiě)并發(fā)應(yīng)用程序并不是一件容易的工作。如果不能正確使用同步機(jī)制,應(yīng)用程序中的任務(wù)就會(huì)出現(xiàn)各種問(wèn)題。本節(jié)將介紹一些此類問(wèn)題。1.2.1數(shù)據(jù)競(jìng)爭(zhēng)如果有兩個(gè)或者多個(gè)任務(wù)在臨界段之外對(duì)一個(gè)共享變量進(jìn)行寫(xiě)入操作,也就是說(shuō)沒(méi)有使用任何同步機(jī)制,那么應(yīng)用程序可能存在數(shù)據(jù)競(jìng)爭(zhēng)(也叫作競(jìng)爭(zhēng)條件)。在這些情況下,應(yīng)用程序的最終結(jié)果可能取決于任務(wù)的執(zhí)行順序。請(qǐng)看下面的例子。packagecom.packt.java.concurrency;publicclassAccount{privatefloatbalance;publicvoidmodify(floatdifference){floatvalue=this.balance;this.balance=value+difference;}}假設(shè)有兩個(gè)不同的任務(wù)執(zhí)行了同一個(gè)Account對(duì)象中的modify()方法。由于任務(wù)中語(yǔ)句的執(zhí)行順序不同,最終結(jié)果也會(huì)有所不同。假設(shè)初始余額為1000,而且兩個(gè)任務(wù)都調(diào)用了modify()方法并采用1000作為參數(shù)。最終的結(jié)果應(yīng)該是3000,但是如果兩個(gè)任務(wù)都在同一時(shí)間執(zhí)行了第一條語(yǔ)句,然后又在同一時(shí)間執(zhí)行了第二條語(yǔ)句,那么最終的結(jié)果將是2000。正如你看到的,modify()方法不是原子的,而Account類也不是線程安全的。1.2.2死鎖當(dāng)兩個(gè)(或多個(gè))任務(wù)正在等待必須由另一線程釋放的某個(gè)共享資源,而該線程又正在等待必須由前述任務(wù)之一釋放的另一共享資源時(shí),并發(fā)應(yīng)用程序就出現(xiàn)了死鎖。當(dāng)系統(tǒng)中同時(shí)出現(xiàn)如下四種條件時(shí),就會(huì)導(dǎo)致這種情形。我們將其稱為Coffman條件?;コ猓核梨i中涉及的資源必須是不可共享的。一次只有一個(gè)任務(wù)可以使用該資源。占有并等待條件:一個(gè)任務(wù)在占有某一互斥的資源時(shí)又請(qǐng)求另一互斥的資源。當(dāng)它在等待時(shí),不會(huì)釋放任何資源。不可剝奪:資源只能被那些持有它們的任務(wù)釋放。循環(huán)等待:任務(wù)1正等待任務(wù)2所占有的資源,而任務(wù)2又正在等待任務(wù)3所占有的資源,以此類推,最終任務(wù)n又在等待由任務(wù)1所占有的資源,這樣就出現(xiàn)了循環(huán)等待。有一些機(jī)制可以用來(lái)避免死鎖。忽略它們:這是最常用的機(jī)制。你可以假設(shè)自己的系統(tǒng)絕不會(huì)出現(xiàn)死鎖,而如果發(fā)生死鎖,結(jié)果就是你可以停止應(yīng)用程序并且重新執(zhí)行它。檢測(cè):系統(tǒng)中有一項(xiàng)專門(mén)分析系統(tǒng)狀態(tài)的任務(wù),可以檢測(cè)是否發(fā)生了死鎖。如果它檢測(cè)到了死鎖,可以采取一些措施來(lái)修復(fù)該問(wèn)題,例如,結(jié)束某個(gè)任務(wù)或者強(qiáng)制釋放某一資源。預(yù)防:如果你想防止系統(tǒng)出現(xiàn)死鎖,就必須預(yù)防Coffman條件中的一條或多條出現(xiàn)。規(guī)避:如果你可以在某一任務(wù)執(zhí)行之前得到該任務(wù)所使用資源的相關(guān)信息,那么死鎖是可以規(guī)避的。當(dāng)一個(gè)任務(wù)要開(kāi)始執(zhí)行時(shí),你可以對(duì)系統(tǒng)中空閑的資源和任務(wù)所需的資源進(jìn)行分析,這樣就可以判斷任務(wù)是否能夠開(kāi)始執(zhí)行。1.2.3活鎖如果系統(tǒng)中有兩個(gè)任務(wù),它們總是因?qū)Ψ降男袨槎淖冏约旱臓顟B(tài),那么就出現(xiàn)了活鎖。最終結(jié)果是它們陷入了狀態(tài)變更的循環(huán)而無(wú)法繼續(xù)向下執(zhí)行。例如,有兩個(gè)任務(wù):任務(wù)1和任務(wù)2,它們都需要用到兩個(gè)資源:資源1和資源2。假設(shè)任務(wù)1對(duì)資源1加了一個(gè)鎖,而任務(wù)2對(duì)資源2加了一個(gè)鎖。當(dāng)它們無(wú)法訪問(wèn)所需的資源時(shí),就會(huì)釋放自己的資源并且重新開(kāi)始循環(huán)。這種情況可以無(wú)限地持續(xù)下去,所以這兩個(gè)任務(wù)都不會(huì)結(jié)束自己的執(zhí)行過(guò)程。1.2.4資源不足當(dāng)某個(gè)任務(wù)在系統(tǒng)中無(wú)法獲取維持其繼續(xù)執(zhí)行所需的資源時(shí),就會(huì)出現(xiàn)資源不足。當(dāng)有多個(gè)任務(wù)在等待某一資源且該資源被釋放時(shí),系統(tǒng)需要選擇下一個(gè)可以使用該資源的任務(wù)。如果你的系統(tǒng)中沒(méi)有設(shè)計(jì)良好的算法,那么系統(tǒng)中有些線程很可能要為獲取該資源而等待很長(zhǎng)時(shí)間。要解決這一問(wèn)題就要確保公平原則。所有等待某一資源的任務(wù)必須在某一給定時(shí)間之內(nèi)占有該資源??蛇x方案之一就是實(shí)現(xiàn)一個(gè)算法,在選擇下一個(gè)將占有某一資源的任務(wù)時(shí),對(duì)任務(wù)已等待該資源的時(shí)間因素加以考慮。然而,實(shí)現(xiàn)鎖的公平需要增加額外的開(kāi)銷,這可能會(huì)降低程序的吞吐量。1.2.5優(yōu)先權(quán)反轉(zhuǎn)當(dāng)一個(gè)低優(yōu)先權(quán)的任務(wù)持有了一個(gè)高優(yōu)先級(jí)任務(wù)所需的資源時(shí),就會(huì)發(fā)生優(yōu)先權(quán)反轉(zhuǎn)。這樣的話,低優(yōu)先權(quán)的任務(wù)就會(huì)在高優(yōu)先權(quán)的任務(wù)之前執(zhí)行。1.3設(shè)計(jì)并發(fā)算法的方法論本節(jié),我們將提出一個(gè)五步驟的方法論來(lái)獲得某一串行算法的并發(fā)版本。該方法論基于Intel公司在其“ThreadingMethodology:PrinciplesandPractices”文檔中給出的方法論。1.3.1起點(diǎn):算法的一個(gè)串行版本我們實(shí)現(xiàn)并發(fā)算法的起點(diǎn)是該算法的一個(gè)串行版本。當(dāng)然,也可以從頭開(kāi)始設(shè)計(jì)一個(gè)并發(fā)算法。不過(guò)我認(rèn)為,算法的串行版本有兩個(gè)方面的好處。我們可以使用串行算法來(lái)測(cè)試并發(fā)算法是否生成了正確的結(jié)果。當(dāng)接收同樣的輸入時(shí),這兩個(gè)版本的算法必須生成同樣的輸出結(jié)果,這樣我們就可以檢測(cè)并發(fā)版本中的一些問(wèn)題,例如數(shù)據(jù)競(jìng)爭(zhēng)或者類似的條件。我們可以度量這兩個(gè)算法的吞吐量,以此來(lái)觀察使用并發(fā)處理是否能夠改善響應(yīng)時(shí)間或者提升算法一次性所能處理的數(shù)據(jù)量。1.3.2第1步:分析在這一步中,我們將分析算法的串行版本來(lái)尋找它的代碼中有哪些部分可以以并行方式執(zhí)行。我們應(yīng)該特別關(guān)注那些執(zhí)行過(guò)程花費(fèi)時(shí)間最多或者執(zhí)行代碼較多的部分,因?yàn)閷?shí)現(xiàn)這些部分的并發(fā)版本將能獲得較大的性能改進(jìn)。對(duì)這一過(guò)程而言,比較好的候選方案就是循環(huán)排查,讓其中的一個(gè)步驟獨(dú)立于其他步驟,或者讓其中某些部分的代碼獨(dú)立于其他部分的代碼(例如一個(gè)用于初始化某個(gè)應(yīng)用程序的算法,它打開(kāi)與數(shù)據(jù)庫(kù)的連接,加載配置文件,初始化一些對(duì)象。所有這些前期任務(wù)都是相互獨(dú)立的)。1.3.3第2步:設(shè)計(jì)一旦你知道了要對(duì)哪些部分的代碼并行處理,就要決定如何對(duì)其進(jìn)行并行化處理了。代碼的變化將影響應(yīng)用程序的兩個(gè)主要部分。代碼的結(jié)構(gòu)。數(shù)據(jù)結(jié)構(gòu)的組織。你可以采用兩種方式來(lái)完成這一任務(wù)。任務(wù)分解:當(dāng)你將代碼劃分成兩個(gè)或多個(gè)可以立刻執(zhí)行的獨(dú)立任務(wù)時(shí),就是在進(jìn)行任務(wù)分解。其中有些任務(wù)可能必須按照某種給定的順序來(lái)執(zhí)行,或者必須在同一點(diǎn)上等待。你必須使用同步機(jī)制來(lái)實(shí)現(xiàn)這樣的行為。數(shù)據(jù)分解:當(dāng)使用同一任務(wù)的多個(gè)實(shí)例分別對(duì)數(shù)據(jù)集的一個(gè)子集進(jìn)行處理時(shí),就是在進(jìn)行數(shù)據(jù)分解。該數(shù)據(jù)集是一個(gè)共享資源,因此,如果這些任務(wù)需要修改數(shù)據(jù),那你必須實(shí)現(xiàn)一個(gè)臨界段來(lái)保護(hù)對(duì)數(shù)據(jù)的訪問(wèn)。另一個(gè)必須牢記的要點(diǎn)是解決方案的粒度。實(shí)現(xiàn)一個(gè)算法的并發(fā)版本,其目標(biāo)在于實(shí)現(xiàn)性能的改善,因此你應(yīng)該使用所有可用的處理器或核。另一方面,當(dāng)你采用某種同步機(jī)制時(shí),就引入了一些額外的必須執(zhí)行的指令。如果你將算法分割成很多小任務(wù)(細(xì)粒度),實(shí)現(xiàn)同步機(jī)制所需額外引入的代碼就會(huì)導(dǎo)致性能下降。如果你將算法分割成比核數(shù)還少的任務(wù)(粗粒度),那么就沒(méi)有充分利用全部資源。同樣,你還要考慮每個(gè)線程都必須要做的工作,尤其是當(dāng)你實(shí)現(xiàn)細(xì)粒度解決方案時(shí)。如果某個(gè)任務(wù)的執(zhí)行時(shí)間比其他任務(wù)長(zhǎng),那么該任務(wù)將決定整個(gè)應(yīng)用程序的執(zhí)行時(shí)間。你需要在這兩點(diǎn)之間找到平衡。1.3.4第3步:實(shí)現(xiàn)下一步就是使用某種編程語(yǔ)言來(lái)實(shí)現(xiàn)并發(fā)算法了,而且如果必要,還要用到線程庫(kù)。在本書(shū)的例子中,我們將使用Java語(yǔ)言來(lái)實(shí)現(xiàn)所有算法。1.3.5第4步:測(cè)試在完成實(shí)現(xiàn)過(guò)程之后,你應(yīng)該對(duì)該并行算法進(jìn)行測(cè)試。如果你有了算法的串行版本,可以對(duì)比這兩個(gè)版本算法的結(jié)果,從而驗(yàn)證并行版本是否正確。測(cè)試和調(diào)試一個(gè)并行程序的具體實(shí)現(xiàn)是非常困難的任務(wù),因?yàn)閼?yīng)用程序中不同任務(wù)的執(zhí)行順序是無(wú)法保證的。在第12章中,你將學(xué)到一些提示、技巧和工具,從而可以高效地完成這些任務(wù)。1.3.6第5步:調(diào)整最后一步是對(duì)比并行算法和串行算法的吞吐量。如果結(jié)果并未達(dá)到預(yù)期,那么你必須重新審查該算法,查找造成并行算法性能較差的原因。你也可以測(cè)試該算法的不同參數(shù)(例如任務(wù)的粒度或數(shù)量),從而找到最佳配置。還有其他一些指標(biāo)可用來(lái)評(píng)估通過(guò)使算法并行處理可能獲得的性能改進(jìn)。下面給出的是最常見(jiàn)的三個(gè)指標(biāo)。加速比(speedup):這是一個(gè)用于評(píng)價(jià)并行版算法和串行版算法之間相對(duì)性能改進(jìn)情況的指標(biāo)。其中,Tsequential是算法串行版的執(zhí)行時(shí)間,而Tconcurrent是算法并行版的執(zhí)行時(shí)間。Amdahl定律:該定律用于計(jì)算對(duì)算法并行化處理之后可獲得的最大期望改進(jìn)。其中,P是可以進(jìn)行并行化處理的代碼的百分比,而N是你準(zhǔn)備用于執(zhí)行該算法的計(jì)算機(jī)的核數(shù)。例如,如果你可以對(duì)75%的代碼進(jìn)行并行化處理并且有四個(gè)核,那么最大加速比可按照如下公式進(jìn)行計(jì)算。Gustafson-Barsis定律1:Amdahl定律具有一定缺陷。它假設(shè)當(dāng)你增加核的數(shù)量時(shí)輸入數(shù)據(jù)集是相同的,但是一般來(lái)說(shuō),當(dāng)擁有更多的核時(shí),你就想處理更多的數(shù)據(jù)。Gustafson定律認(rèn)為,當(dāng)你有更多可用的核時(shí),可同時(shí)解決的問(wèn)題規(guī)模就越大,其公式如下其中,N為核數(shù),而P為可并行處理代碼所占的百分比。如果我們使用之前的同一示例,那么Gustafson定律計(jì)算出的可伸縮加速比如下。1也稱作Gustafson定律?!g者注1.3.7結(jié)論在本節(jié)中,你知曉了在對(duì)某一串行算法進(jìn)行并行化處理時(shí)必須考慮的問(wèn)題。首先,并非每一個(gè)算法都可以進(jìn)行并行化處理。例如,如果你要執(zhí)行一個(gè)循環(huán),其每次迭代的結(jié)果取決于前一次迭代的結(jié)果,那么你就不能對(duì)該循環(huán)進(jìn)行并行化處理?;谕瑯拥脑?,遞歸算法是無(wú)法進(jìn)行并行化處理的另一個(gè)例子。你要牢記的另一重要事項(xiàng)是:對(duì)性能良好的串行版算法實(shí)現(xiàn)并行處理,實(shí)際上是個(gè)糟糕的出發(fā)點(diǎn)。如果在你開(kāi)始對(duì)某個(gè)算法進(jìn)行并行化處理時(shí),發(fā)現(xiàn)并不容易找到代碼的獨(dú)立部分,那么你就要找一找該算法的其他版本,并且驗(yàn)證一下該版本的算法是否能夠很方便地進(jìn)行并行化處理。最后,當(dāng)你實(shí)現(xiàn)一個(gè)并發(fā)應(yīng)用程序時(shí)(從頭開(kāi)始或者基于一個(gè)串行算法),必須要考慮下面幾點(diǎn)。效率:并行版算法花費(fèi)的時(shí)間必須比串行版算法少。對(duì)算法進(jìn)行并行處理的首要目標(biāo)就是實(shí)現(xiàn)運(yùn)行時(shí)間比串行版算法少,或者說(shuō)它能夠在相同時(shí)間內(nèi)處理更多的數(shù)據(jù)。簡(jiǎn)單:當(dāng)你實(shí)現(xiàn)一個(gè)算法(無(wú)論是否為并行算法)時(shí),必須盡可能確保其簡(jiǎn)單。它應(yīng)該更加容易實(shí)現(xiàn)、測(cè)試、調(diào)試和維護(hù),這樣就會(huì)少出錯(cuò)??梢浦残裕耗愕牟⑿兴惴☉?yīng)該只需要很少的更改就能夠在不同的平臺(tái)上執(zhí)行。因?yàn)樵诒緯?shū)中使用Java語(yǔ)言,所以做到這一點(diǎn)非常簡(jiǎn)單。有了Java,你就可以在每一種操作系統(tǒng)中執(zhí)行程序而無(wú)須任何更改(除非因?yàn)槌绦驅(qū)崿F(xiàn)而必須更改)。伸縮性:如果你增加了核的數(shù)目,算法會(huì)發(fā)生什么情況?正如前面提到的,你應(yīng)該使用所有可用的核,這樣一來(lái)你的算法就能利用所有可用的資源。1.4Java并發(fā)APIJava編程語(yǔ)言含有非常豐富的并發(fā)API。它含有管理基本并發(fā)元素所需的類,例如Thread、Lock和Semaphore等類,以及用于實(shí)現(xiàn)非常高層同步機(jī)制的類,例如執(zhí)行器框架或新增加的并行StreamAPI。本節(jié)將涵蓋形成并發(fā)API的基本類。1.4.1基本并發(fā)類并發(fā)API的基本類如下。Thread類:該類描述了執(zhí)行并發(fā)Java應(yīng)用程序的所有線程。Runnable接口:這是Java中創(chuàng)建并發(fā)應(yīng)用程序的另一種方式。ThreadLocal類:該類用于存放從屬于某一線程的變量。ThreadFactory接口:這是實(shí)現(xiàn)Factory設(shè)計(jì)模式的基類,你可以用它來(lái)創(chuàng)建定制線程。1.4.2同步機(jī)制Java并發(fā)API包括多種同步機(jī)制,可以支持你:定義用于訪問(wèn)某一共享資源的臨界段;在某一共同點(diǎn)上同步不同的任務(wù)。下面是最重要的同步機(jī)制。synchronized關(guān)鍵字:synchronized關(guān)鍵字允許你在某個(gè)代碼塊或者某個(gè)完整的方法中定義一個(gè)臨界段。Lock接口:Lock提供了比synchronized關(guān)鍵字更為靈活的同步操作。Lock接口有多種不同類型:ReentrantLock用于實(shí)現(xiàn)一個(gè)可與某種條件相關(guān)聯(lián)的鎖;ReentrantReadWriteLock將讀寫(xiě)操作分離開(kāi)來(lái);StampedLock是Java8中增加的一種新特性,它包括三種控制讀/寫(xiě)訪問(wèn)的模式。Semaphore類:該類通過(guò)實(shí)現(xiàn)經(jīng)典的信號(hào)量機(jī)制來(lái)實(shí)現(xiàn)同步。Java支持二進(jìn)制信號(hào)量和一般信號(hào)量。CountDownLatch類:該類允許一個(gè)任務(wù)等待多項(xiàng)操作的結(jié)束。CyclicBarrier類:該類允許多線程在某一共同點(diǎn)上進(jìn)行同步。Phaser類:該類允許你控制那些分割成多個(gè)階段的任務(wù)的執(zhí)行。在所有任務(wù)都完成當(dāng)前階段之前,任何任務(wù)都不能進(jìn)入下一階段。1.4.3執(zhí)行器執(zhí)行器框架是在實(shí)現(xiàn)并發(fā)任務(wù)時(shí)將線程的創(chuàng)建和管理分割開(kāi)來(lái)的一種機(jī)制。你不必?fù)?dān)心線程的創(chuàng)建和管理,只需要關(guān)心任務(wù)的創(chuàng)建并且將其發(fā)送給執(zhí)行器。該框架中涉及的主要類如下。Executor接口和ExecutorService接口:它們包含了所有執(zhí)行器共有的execute()方法。ThreadPoolExecutor類:該類允許你獲取一個(gè)含有線程池的執(zhí)行器,而且可以定義并行任務(wù)的最大數(shù)目。ScheduledThreadPoolExecutor類:這是一種特殊的執(zhí)行器,可以使你在某段延遲之后執(zhí)行任務(wù)或者周期性執(zhí)行任務(wù)。Executors:該類使執(zhí)行器的創(chuàng)建更為容易。Callable接口:這是Runnable接口的替代接口——可返回值的一個(gè)單獨(dú)的任務(wù)。Future接口:該接口包含了一些能獲取Callable接口返回值并且控制其狀態(tài)的方法。1.4.4Fork/Join框架Fork/Join框架定義了一種特殊的執(zhí)行器,尤其針對(duì)采用分治方法進(jìn)行求解的問(wèn)題。針對(duì)解決這類問(wèn)題的并發(fā)任務(wù),它還提供了一種優(yōu)化其執(zhí)行的機(jī)制。Fork/Join是為細(xì)粒度并行處理量身定制的,因?yàn)樗拈_(kāi)銷非常小,這也是將新任務(wù)加入隊(duì)列中并且按照隊(duì)列排序執(zhí)行任務(wù)的需要。該框架涉及的主要類和接口如下。ForkJoinPool:該類實(shí)現(xiàn)了要用于運(yùn)行任務(wù)的執(zhí)行器。ForkJoinTask:這是一個(gè)可以在ForkJoinPool類中執(zhí)行的任務(wù)。ForkJoinWorkerThread:這是一個(gè)準(zhǔn)備在ForkJoinPool類中執(zhí)行任務(wù)的線程。1.4.5并行流流和lambda表達(dá)式可能是Java8中最重要的兩個(gè)新特性。流已經(jīng)被增加為Collection接口和其他一些數(shù)據(jù)源的方法,它允許處理某一數(shù)據(jù)結(jié)構(gòu)的所有元素、生成新的結(jié)構(gòu)、篩選數(shù)據(jù)和使用MapReduce方法來(lái)實(shí)現(xiàn)算法。并行流是一種特殊的流,它以一種并行方式實(shí)現(xiàn)其操作。使用并行流時(shí)涉及的最重要的元素如下。Stream接口:該接口定義了所有可以在一個(gè)流上實(shí)施的操作。Optional:這是一個(gè)容器對(duì)象,可能(也可能不)包含一個(gè)非空值。Collectors:該類實(shí)現(xiàn)了約簡(jiǎn)(reduction)操作,而該操作可作為流操作序列的一部分使用。lambda表達(dá)式:流被認(rèn)為是可以處理lambda表達(dá)式的。大多數(shù)流方法都會(huì)接收一個(gè)lambda表達(dá)式作為參數(shù),這讓你可以實(shí)現(xiàn)更為緊湊的操作。1.4.6并發(fā)數(shù)據(jù)結(jié)構(gòu)JavaAPI中的常見(jiàn)數(shù)據(jù)結(jié)構(gòu)(例如ArrayList、Hashtable等)并不能在并發(fā)應(yīng)用程序中使用,除非采用某種外部同步機(jī)制。但是如果你采用了某種同步機(jī)制,應(yīng)用程序就會(huì)增加大量的額外計(jì)算時(shí)間。而如果你不采用同步機(jī)制,那么應(yīng)用程序中很可能出現(xiàn)競(jìng)爭(zhēng)條件。如果你在多個(gè)線程中修改數(shù)據(jù),那么就會(huì)出現(xiàn)競(jìng)爭(zhēng)條件,你可能會(huì)面對(duì)各種異常(例如ConcurrentModificationException和ArrayIndexOutOfBoundsException),出現(xiàn)隱性數(shù)據(jù)丟失,或者應(yīng)用程序會(huì)陷入死循環(huán)。Java并發(fā)API中含有大量可以在并發(fā)應(yīng)用中使用而沒(méi)有風(fēng)險(xiǎn)的數(shù)據(jù)結(jié)構(gòu)。我們將它們分為以下兩大類別。阻塞型數(shù)據(jù)結(jié)構(gòu):這些數(shù)據(jù)結(jié)構(gòu)含有一些能夠阻塞調(diào)用任務(wù)的方法,例如,當(dāng)數(shù)據(jù)結(jié)構(gòu)為空而你又要從中獲取值時(shí)。非阻塞型數(shù)據(jù)結(jié)構(gòu):如果操作可以立即進(jìn)行,它并不會(huì)阻塞調(diào)用任務(wù)。否則,它將返回null值或者拋出異常。下面是其中的一些數(shù)據(jù)結(jié)構(gòu)。ConcurrentLinkedDeque:這是一個(gè)非阻塞型的列表。ConcurrentLinkedQueue:這是一個(gè)非阻塞型的隊(duì)列。LinkedBlockingDeque:這是一個(gè)阻塞型的列表。LinkedBlockingQueue:這是一個(gè)阻塞型的隊(duì)列。PriorityBlockingQueue:這是一個(gè)基于優(yōu)先級(jí)對(duì)元素進(jìn)行排序的阻塞型隊(duì)列。ConcurrentSkipListMap:這是一個(gè)非阻塞型的NavigableMap。ConcurrentHashMap:這是一個(gè)非阻塞型的哈希表。AtomicBoolean、AtomicInteger、AtomicLong和AtomicReference:這些是基本Java數(shù)據(jù)類型的原子實(shí)現(xiàn)。1.5并發(fā)設(shè)計(jì)模式在軟件工程中,設(shè)計(jì)模式是針對(duì)某一類共同問(wèn)題的解決方案。這種解決方案被多次使用,而且已經(jīng)被證明是針對(duì)該類問(wèn)題的最優(yōu)解決方案。每當(dāng)你需要解決這其中的某個(gè)問(wèn)題,就可以使用它們來(lái)避免做重復(fù)工作。其中,單例模式(Singleton)和工廠模式(Factory)是幾乎每個(gè)應(yīng)用程序中都要用到的通用設(shè)計(jì)模式。并發(fā)處理也有其自己的設(shè)計(jì)模式。本節(jié),我們將介紹一些最常用的并發(fā)設(shè)計(jì)模式,以及它們的Java語(yǔ)言實(shí)現(xiàn)。1.5.1信號(hào)模式這種設(shè)計(jì)模式介紹了如何實(shí)現(xiàn)某一任務(wù)向另一任務(wù)通告某一事件的情形。實(shí)現(xiàn)這種設(shè)計(jì)模式最簡(jiǎn)單的方式是采用信號(hào)量或者互斥,使用Java語(yǔ)言中的ReentrantLock類或Semaphore類即可,甚至可以采用Object類中的wait()方法和notify()方法。請(qǐng)看下面的例子。publicvoidtask1(){section1();commonObject.notify();}publicvoidtask2(){commonObject.wait();section2();}在上述情況下,section2()方法總是在section1()方法之后執(zhí)行。1.5.2會(huì)合模式這種設(shè)計(jì)模式是信號(hào)模式的推廣。在這種情況下,第一個(gè)任務(wù)將等待第二個(gè)任務(wù)的某一事件,而第二個(gè)任務(wù)又在等待第一個(gè)任務(wù)的某一事件。其解決方案和信號(hào)模式非常相似,只不過(guò)在這種情況下,你必須使用兩個(gè)對(duì)象而不是一個(gè)。請(qǐng)看下面的例子。publicvoidtask1(){section1_1();commonObject1.notify();commonObject2.wait();section1_2();}publicvoidtask2(){section2_1();commonObject2.notify();commonObject1.wait();section2_2();}在上述情況下,section2_2()方法總是會(huì)在section1_1()方法之后執(zhí)行,而section1_2()方法總是會(huì)在section2_1()方法之后執(zhí)行。仔細(xì)想想就會(huì)發(fā)現(xiàn),如果你將對(duì)wait()方法的調(diào)用放在對(duì)notify()方法的調(diào)用之前,那么就會(huì)出現(xiàn)死鎖。1.5.3互斥模式互斥這種機(jī)制可以用來(lái)實(shí)現(xiàn)臨界段,確保操作相互排斥。這就是說(shuō),一次只有一個(gè)任務(wù)可以執(zhí)行由互斥機(jī)制保護(hù)的代碼片段。在Java中,你可以使用synchronized關(guān)鍵字(這允許你保護(hù)一段代碼或者一個(gè)完整的方法)、ReentrantLock類或者Semaphore類來(lái)實(shí)現(xiàn)一個(gè)臨界段。讓我們看看下面的例子。publicvoidtask(){preCriticalSection();try{lockObject.lock()//臨界段開(kāi)始criticalSection();}catch(Exceptione){}finally{lockObject.unlock();//臨界段結(jié)束postCriticalSection();}1.5.4多元復(fù)用模式多元復(fù)用設(shè)計(jì)模式是互斥機(jī)制的推廣。在這種情形下,規(guī)定數(shù)目的任務(wù)可以同時(shí)執(zhí)行臨界段。這很有用,例如,當(dāng)你擁有某一資源的多個(gè)副本時(shí)。在Java中實(shí)現(xiàn)這種設(shè)計(jì)模式最簡(jiǎn)單的方式是使用Semaphore類,并且使用可同時(shí)執(zhí)行臨界段的任務(wù)數(shù)來(lái)初始化該類。請(qǐng)看如下示例。publicvoidtask(){preCriticalSection();semaphoreObject.acquire();criticalSection();semaphoreObject.release();postCriticalSection();}1.5.5柵欄模式這種設(shè)計(jì)模式解釋了如何在某一共同點(diǎn)上實(shí)現(xiàn)任務(wù)同步的情形。每個(gè)任務(wù)都必須等到所有任務(wù)都到達(dá)同步點(diǎn)后才能繼續(xù)執(zhí)行。Java并發(fā)API提供了CyclicBarrier類,它是這種設(shè)計(jì)模式的一個(gè)實(shí)現(xiàn)。請(qǐng)看下面的例子。publicvoidtask(){preSyncPoint();barrierObject.await();postSyncPoint();}1.5.6雙重檢查鎖定模式當(dāng)你獲得某個(gè)鎖之后要檢查某項(xiàng)條件時(shí),這種設(shè)計(jì)模式可以為解決該問(wèn)題提供方案。如果該條件為假,你實(shí)際上也已經(jīng)花費(fèi)了獲取到理想的鎖所需的開(kāi)銷。對(duì)象的延遲初始化就是針對(duì)這種情形的例子。如果你有一個(gè)類實(shí)現(xiàn)了單例設(shè)計(jì)模式,那可能會(huì)有如下這樣的代碼。publicclassSingleton{privatestaticSingletonreference;privatestaticfinalLocklock=newReentrantLock();publicstaticSingletongetReference(){try{lock.lock();if(reference==null){reference=newObject();}}catch(Exceptione){System.out.println(e);}finally{lock.unlock();}returnreference;}}一種可能的解決方案就是在條件之中包含鎖。publicclassSingleton{privateObjectreference;privateLocklock=newReentrantLock();publicObjectgetReference(){if(reference==null){lock.lock();if(reference==null){reference=newObject();}lock.unlock();}returnreference;}}該解決方案仍然存在問(wèn)題。如果兩個(gè)任務(wù)同時(shí)檢查條件,你將要?jiǎng)?chuàng)建兩個(gè)對(duì)象。解決這一問(wèn)題的最佳方案就是不使用任何顯式的同步機(jī)制。publicclassSingleton{privatestaticclassLazySingleton{privatestaticfinalSingletonINSTANCE=newSingleton();}publicstaticSingletongetSingleton(){returnLazySingleton.INSTANCE;}}1.5.7讀-寫(xiě)鎖模式當(dāng)你使用鎖來(lái)保護(hù)對(duì)某個(gè)共享變量的訪問(wèn)時(shí),只有一個(gè)任務(wù)可以訪問(wèn)該變量,這和你將要對(duì)該變量實(shí)施的操作是相互獨(dú)立的。有時(shí),你的變量需要修改的次數(shù)很少,卻需要讀取很多次。這種情況下,鎖的性能就會(huì)比較差了,因?yàn)樗凶x操作都可以并發(fā)進(jìn)行而不會(huì)帶來(lái)任何問(wèn)題。為解決這樣的問(wèn)題,出現(xiàn)了讀-寫(xiě)鎖設(shè)計(jì)模式。這種模式定義了一種特殊的鎖,它含有兩個(gè)內(nèi)部鎖:一個(gè)用于讀操作,而另一個(gè)用于寫(xiě)操作。該鎖的行為特點(diǎn)如下所示。如果一個(gè)任務(wù)正在執(zhí)行讀操作而另一任務(wù)想要進(jìn)行另一個(gè)讀操作,那么另一任務(wù)可以進(jìn)行該操作。如果一個(gè)任務(wù)正在執(zhí)行讀操作而另一任務(wù)想要進(jìn)行寫(xiě)操作,那么另一任務(wù)將被阻塞,直到所有的讀取方都完成操作為止。如果一個(gè)任務(wù)正在執(zhí)行寫(xiě)操作而另一任務(wù)想要執(zhí)行另一操作(讀或者寫(xiě)),那么另一任務(wù)將被阻塞,直到寫(xiě)入方完成操作為止。Java并發(fā)API中含有ReentrantReadWriteLock類,該類實(shí)現(xiàn)了這種設(shè)計(jì)模式。如果你想從頭開(kāi)始實(shí)現(xiàn)該設(shè)計(jì)模式,就必須非常注意讀任務(wù)和寫(xiě)任務(wù)之間的優(yōu)先級(jí)。如果有太多讀任務(wù)存在,那么寫(xiě)任務(wù)等待的時(shí)間就會(huì)很長(zhǎng)。1.5.8線程池模式這種設(shè)計(jì)模式試圖減少為執(zhí)行每個(gè)任務(wù)而創(chuàng)建線程所引入的開(kāi)銷。該模式由一個(gè)線程集合和一個(gè)待執(zhí)行的任務(wù)隊(duì)列構(gòu)成。線程集合通常具有固定大小。當(dāng)一個(gè)線程完成了某個(gè)任務(wù)的執(zhí)行時(shí),它本身并不會(huì)結(jié)束執(zhí)行,它要尋找隊(duì)列中的另一個(gè)任務(wù)。如果存在另一個(gè)任務(wù),那么它將執(zhí)行該任務(wù)。如果不存在另一個(gè)任務(wù),那么該線程將一直等待,直到有任務(wù)插入隊(duì)列中為止,但是線程本身不會(huì)被終結(jié)。Java并發(fā)API包含一些實(shí)現(xiàn)ExecutorService接口的類,該接口內(nèi)部采用了一個(gè)線程池。1.5.9線程局部存儲(chǔ)模式這種設(shè)計(jì)模式定義了如何使用局部從屬于任務(wù)的全局變量或靜態(tài)變量。當(dāng)在某個(gè)類中有一個(gè)靜態(tài)屬性時(shí),那么該類的所有對(duì)象都會(huì)訪問(wèn)該屬性的同一存在。如果使用了線程局部存儲(chǔ),則每個(gè)線程都會(huì)訪問(wèn)該變量的一個(gè)不同實(shí)例。Java并發(fā)API包含了ThreadLocal類,該類實(shí)現(xiàn)了這種設(shè)計(jì)模式。1.6設(shè)計(jì)并發(fā)算法的提示和技巧本節(jié)匯編了一些需要你牢記的提示和技巧,它們可以幫助你設(shè)計(jì)出良好的并發(fā)應(yīng)用程序。1.6.1正確識(shí)別獨(dú)立任務(wù)你只能執(zhí)行那些相互獨(dú)立的并發(fā)任務(wù)。如果兩個(gè)或多個(gè)任務(wù)之間存在某種順序依賴,你可能沒(méi)興趣嘗試以并發(fā)方式執(zhí)行它們,同時(shí)引入某種同步機(jī)制來(lái)保證執(zhí)行順序。這些任務(wù)將以串行方式執(zhí)行,而你還必須使用同步機(jī)制。另一種不同的場(chǎng)景是,你的任務(wù)具有一些先決條件,但是這些先決條件都是相互獨(dú)立的。在這種情形下,你可以以并發(fā)方式執(zhí)行這些先決條件,然后在完成先決條件后使用一個(gè)同步類來(lái)控制任務(wù)的執(zhí)行。另一個(gè)無(wú)法使用并發(fā)處理的場(chǎng)景是,你有一個(gè)循環(huán),而所有步驟所使用的數(shù)據(jù)都是由它之前的步驟生成的,或者存在一些需要從一個(gè)步驟流轉(zhuǎn)到下一步驟的狀態(tài)信息。1.6.2在盡可能高的層面上實(shí)施并發(fā)處理像Java并發(fā)API這樣豐富的線程處理API,為你在應(yīng)用程序中實(shí)現(xiàn)并發(fā)處理提供了不同的類。對(duì)于Java來(lái)說(shuō),你可以使用Thread類或Lock類來(lái)控制線程的創(chuàng)建和同步,不過(guò)Java也提供了高層次的并發(fā)處理對(duì)象,例如執(zhí)行器或Fork/Join框架,它們都可以支持你執(zhí)行并發(fā)任務(wù)。這種高層機(jī)制有下述好處。你不需要擔(dān)心線程的創(chuàng)建和管理,只需要?jiǎng)?chuàng)建并且發(fā)送任務(wù)以使其執(zhí)行。Java并發(fā)API會(huì)幫助你控制線程的創(chuàng)建和管理。它們都經(jīng)過(guò)了優(yōu)化,可以比直接使用線程提供更好的性能。例如,它們使用了一個(gè)線程池,可對(duì)線程進(jìn)行重用,避免了為每個(gè)任務(wù)都創(chuàng)建線程。你可以從頭開(kāi)始實(shí)現(xiàn)這些機(jī)制,但是這會(huì)花費(fèi)你大量的時(shí)間,而且這也是一項(xiàng)復(fù)雜的任務(wù)。它們含有一些高級(jí)特性,可以使API更加強(qiáng)大。例如,有了Java中的執(zhí)行器,你可以執(zhí)行以Future對(duì)象形式返回結(jié)果的任務(wù)。同樣,你也可以從頭開(kāi)始實(shí)現(xiàn)這些機(jī)制,但是并不建議這樣做。你的應(yīng)用程序很容易從一個(gè)操作系統(tǒng)被遷移到另一個(gè),而且它將具有更好的伸縮性。你的應(yīng)用程序在今后的Java版本中可能會(huì)更加快速。Java開(kāi)發(fā)人員一直都在改進(jìn)內(nèi)部構(gòu)件,而且JVM優(yōu)化也會(huì)更加適合于JDKAPI。總之,出于性能和開(kāi)發(fā)時(shí)間方面的原因,在實(shí)現(xiàn)并發(fā)算法之前,要分析一下線程API提供的高層機(jī)制。1.6.3考慮伸縮性若是要實(shí)現(xiàn)一個(gè)并發(fā)算法,主要目標(biāo)之一就是要利用計(jì)算機(jī)的全部資源,尤其是要充分利用處理器或者核的數(shù)目。但是這個(gè)數(shù)目可能會(huì)隨時(shí)間推移而發(fā)生變化。硬件是不斷改進(jìn)的,而且其成本每年都在降低。當(dāng)你使用數(shù)據(jù)分解來(lái)設(shè)計(jì)并發(fā)算法時(shí),不要預(yù)先假定應(yīng)用程序要在多少個(gè)核或者處理器上執(zhí)行。要?jiǎng)討B(tài)獲取系統(tǒng)的有關(guān)信息(例如,在Java中可以使用Runtime.getRuntime().availableProcessors()方法來(lái)獲取信息),并且讓你的算法使用這些信息來(lái)計(jì)算它要執(zhí)行的任務(wù)數(shù)。這個(gè)過(guò)程會(huì)給算法執(zhí)行時(shí)間帶來(lái)額外開(kāi)銷,但是你的算法將有更好的伸縮性。如果你使用任務(wù)分解來(lái)設(shè)計(jì)并發(fā)算法,情況就會(huì)更加復(fù)雜。你要根據(jù)算法中獨(dú)立任務(wù)的數(shù)目來(lái)設(shè)計(jì),而且強(qiáng)制執(zhí)行較多的任務(wù)將會(huì)增加由同步機(jī)制引入的開(kāi)銷,而且應(yīng)用程序的整體性能甚至?xí)愀?。要詳?xì)分析算法來(lái)判斷是否要采用動(dòng)態(tài)的任務(wù)數(shù)。1.6.4使用線程安全API如果你需要在并發(fā)應(yīng)用程序中使用某個(gè)Java庫(kù),首先要閱讀其文檔以了解該庫(kù)是否為線程安全的。如果它是線程安全的,那么你可以在自己的應(yīng)用程序中使用它而不會(huì)出現(xiàn)任何問(wèn)題。如果它不是線程安全的,那么你有如下兩個(gè)選擇。如果已經(jīng)存在一個(gè)線程安全的替代方案,那么就應(yīng)該使用該替代方案。如果不存在線程安全的替代方案,就應(yīng)該添加必要的同步機(jī)制來(lái)避免所有可能出現(xiàn)問(wèn)題的情形,尤其是數(shù)據(jù)競(jìng)爭(zhēng)條件。例如,如果你在并發(fā)應(yīng)用程序中需要用到一個(gè)List,且需要在多個(gè)線程中對(duì)其更新,那么就不應(yīng)該使用ArrayList類,因?yàn)樗皇蔷€程安全的。在這種情況下,你可以使用一個(gè)線程安全的類,例如ConcurrentLinkedDeque、CopyOnWriteArrayList或者LinkedBlockingDeque。如果你要用的類不是線程安全的,你必須首先查找一個(gè)線程安全的替代方案。采用并發(fā)API很可能比你所能實(shí)現(xiàn)的任何替代方案都更加優(yōu)化。1.6.5絕不要假定執(zhí)行順序如果你不采用任何同步機(jī)制,那么在并發(fā)應(yīng)用程序中任務(wù)的執(zhí)行順序是不確定的。任務(wù)執(zhí)行的順序以及每個(gè)任務(wù)執(zhí)行的時(shí)間,是由操作系統(tǒng)的調(diào)度器所決定的。在多次執(zhí)行時(shí),調(diào)度器并不關(guān)心執(zhí)行順序是否相同。下一次執(zhí)行時(shí)順序可能就不同了。假定某一執(zhí)行順序的結(jié)果通常會(huì)導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。算法的最終結(jié)果取決于任務(wù)執(zhí)行的順序。有時(shí),結(jié)果可能是正確的,但在其他時(shí)候可能是錯(cuò)誤的。檢測(cè)導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)條件的原因非常困難,因此你必須小心謹(jǐn)慎,不要忘記所有必須進(jìn)行同步的元素。1.6.6在靜態(tài)和共享場(chǎng)合盡可能使用局部線程變量線程局部變量是一種特殊的變量。每個(gè)任務(wù)針對(duì)該變量都有一個(gè)獨(dú)立的值,這樣你就不需要任何同步機(jī)制來(lái)保護(hù)對(duì)該變量的訪問(wèn)。這聽(tīng)起來(lái)有些奇怪。對(duì)于該類的各個(gè)屬性,每個(gè)對(duì)象都有自己的一個(gè)副本,那么為什么我們還需要線程局部變量呢?試想這樣的場(chǎng)景:你創(chuàng)建了一個(gè)Runnable任務(wù),而且你也想執(zhí)行該任務(wù)的多個(gè)實(shí)例。你可以為要執(zhí)行的每個(gè)線程都創(chuàng)建一個(gè)Runnable對(duì)象,但另一個(gè)可選方案是創(chuàng)建一個(gè)Runnable對(duì)象并且使用該對(duì)象創(chuàng)建所有線程。在后一種情況中,所有線程都將訪問(wèn)該類各屬性的同一副本,除非你使用ThreadLocal類。ThreadLocal類確保了每個(gè)線程都將訪問(wèn)自己針對(duì)該變量的實(shí)例,而不需要使用Lock類、Semaphore類或者類似的類。另一種場(chǎng)景是,你所使用的Thread局部變量帶有靜態(tài)屬性。此時(shí),類的所有實(shí)例都會(huì)共享其靜態(tài)屬性,除非你使用ThreadLocal類來(lái)聲明它們。在使用ThreadLocal類聲明的情況下,每個(gè)線程都訪問(wèn)其自己的副本。另一個(gè)可選方案是使用ConcurrentHashMap<Thread,MyType>這樣的方式,像var.get(Thread.currentThread())或var.put(Thread.currentThread(),newValue)這樣使用它。通常,由于可能出現(xiàn)競(jìng)爭(zhēng),這種方式要比采用ThreadLocal的方式明顯慢一些(采用ThreadLocal根本就沒(méi)有競(jìng)爭(zhēng))。不過(guò)這種方式也有其優(yōu)點(diǎn):你可以完全清空哈希表,這樣對(duì)每個(gè)線程來(lái)說(shuō)其中的值都會(huì)消失。因此,采用這種方式有時(shí)也是有用的。1.6.7尋找更易于并行處理的算法版本我們將算法定義為解決某一問(wèn)題的一系列步驟。解決同一問(wèn)題可以有許多方式。有些方式速度更快,有些方式使用的資源更少,還有一些方式能夠更好地適應(yīng)輸入數(shù)據(jù)的特定特征。例如,如果你想要對(duì)一組數(shù)排序,可以使用已實(shí)現(xiàn)的多種排序算法之一來(lái)解決問(wèn)題。在前一節(jié)中,我們推薦你使用串行版算法作為實(shí)現(xiàn)并發(fā)算法的起點(diǎn)。這種方式主要有兩個(gè)優(yōu)點(diǎn)。很容易測(cè)試并行算法結(jié)果的正確性。可以度量采用并發(fā)處理后獲得的性能提升。但是并非每個(gè)算法都可以并行化處理,至少并不那么容易。你可能認(rèn)為最好的起點(diǎn)是解決待并行處理的問(wèn)題的性能最佳的串行算法,但這是一種錯(cuò)誤的假設(shè)。你應(yīng)該尋找更容易并行化的算法,然后將該并發(fā)算法和其性能最佳的串行版本對(duì)比,看看哪個(gè)可以提供更高的吞吐量。1.6.8盡可能使用不可變對(duì)象在并發(fā)應(yīng)用程序中遇到的一個(gè)主要問(wèn)題就是數(shù)據(jù)競(jìng)爭(zhēng)條件。前文已經(jīng)提到,如果兩個(gè)或多個(gè)任務(wù)能修改在某個(gè)共享變量中存放的數(shù)據(jù),卻沒(méi)有在臨界段中實(shí)現(xiàn)對(duì)該變量的訪問(wèn),就會(huì)發(fā)生數(shù)據(jù)競(jìng)爭(zhēng)條件這樣的情況。例如,當(dāng)你使用Java這樣的面向?qū)ο蟮恼Z(yǔ)言時(shí),可以將應(yīng)用程序作為一個(gè)對(duì)象集合來(lái)實(shí)現(xiàn)。每個(gè)對(duì)象都有一些屬性,還有一些方法用來(lái)讀取和更改這些屬性的值。如果有些任務(wù)共享了某個(gè)對(duì)象,那么當(dāng)你調(diào)用某個(gè)沒(méi)有同步機(jī)制保護(hù)的方法來(lái)更改該對(duì)象某個(gè)屬性的值時(shí),就很可能會(huì)出現(xiàn)數(shù)據(jù)不一致問(wèn)題。有一些特殊的對(duì)象叫作不可變對(duì)象,其主要特征是初始化之后你不能對(duì)其任何屬性進(jìn)行修改。如果你想要修改某一屬性的值,必須創(chuàng)建另一個(gè)對(duì)象。Java中的String類是不可變對(duì)象的最佳例子。當(dāng)你使用某種看起來(lái)會(huì)改變String對(duì)象值的運(yùn)算符(例如=或+=)時(shí),實(shí)際上創(chuàng)建了一個(gè)新的對(duì)象。在并發(fā)應(yīng)用程序中使用不可變對(duì)象有如下兩個(gè)非常重要的好處。不需要任何同步機(jī)制來(lái)保護(hù)這些類的方法。如果兩個(gè)任務(wù)要修改同一對(duì)象,它們將創(chuàng)建新的對(duì)象,因此絕不會(huì)出現(xiàn)兩個(gè)任務(wù)同時(shí)修改同一對(duì)象的情況。不會(huì)有任何數(shù)據(jù)不一致問(wèn)題,因?yàn)檫@是第一點(diǎn)的必然結(jié)果。不可變對(duì)象存在一個(gè)缺點(diǎn)。如果你創(chuàng)建了太多的對(duì)象,可能會(huì)影響應(yīng)用程序的吞吐量和內(nèi)存使用。如果你有一個(gè)沒(méi)有內(nèi)部數(shù)據(jù)結(jié)構(gòu)的簡(jiǎn)單對(duì)象,將其作為不可變對(duì)象通常是沒(méi)有問(wèn)題的。然而,構(gòu)造由其他對(duì)象集合整合而成的復(fù)雜不可變對(duì)象通常會(huì)導(dǎo)致嚴(yán)重的性能問(wèn)題。1.6.9通過(guò)對(duì)鎖排序來(lái)避免死鎖在并發(fā)應(yīng)用程序中避免死鎖的最佳機(jī)制之一是強(qiáng)制要求任務(wù)總是以相同順序獲取資源。實(shí)現(xiàn)這種機(jī)制的一種簡(jiǎn)單方式是為每個(gè)資源都分配一個(gè)編號(hào)。當(dāng)一個(gè)任務(wù)需要多個(gè)資源時(shí),它需要按照順序來(lái)請(qǐng)求。例如,你有兩個(gè)任務(wù)T1和T2,它們都需要兩項(xiàng)資源R1和R2,你可以強(qiáng)制它們首先請(qǐng)求R1資源然后請(qǐng)求R2資源,這樣就不會(huì)發(fā)生死鎖。另一方面,如果T1首先請(qǐng)求了R1資源然后請(qǐng)求R2資源,并且T2首先請(qǐng)求了R2資源然后請(qǐng)求R1資源,那么就會(huì)發(fā)生死鎖。這一技巧的一種錯(cuò)誤使用如下所示。你有兩個(gè)任務(wù)都需要獲得兩個(gè)Lock對(duì)象,它們都試圖以不同順序來(lái)獲取鎖。publicvoidoperation1(){lock1.lock();lock2.lock();.}publicvoidoperation2(){lock2.lock();lock1.lock();}可能operation1()方法執(zhí)行了它的第一條語(yǔ)句,而operation2()方法也執(zhí)行了它的第一條語(yǔ)句,這樣它們都將等待另一個(gè)鎖,也就發(fā)生了死鎖。只要按照同樣的順序獲取鎖,就可以避免這一點(diǎn)。如果按照下述代碼更改operation2()方法,就絕不會(huì)發(fā)生死鎖。publicvoidoperation2(){lock1.lock();lock2.lock();}1.6.10使用原子變量代替同步當(dāng)你要在兩個(gè)或者多個(gè)任務(wù)之間共享數(shù)據(jù)時(shí),必須使用同步機(jī)制來(lái)保護(hù)對(duì)該數(shù)據(jù)的訪問(wèn),并且避免任何數(shù)據(jù)不一致問(wèn)題。某些情況下,你可以使用volatile關(guān)鍵字而不使用同步機(jī)制。如果只有一個(gè)任務(wù)修改數(shù)據(jù)而其他任務(wù)都讀取數(shù)據(jù),那么你可以使用volatile關(guān)鍵字而無(wú)須任何同步機(jī)制,并且不會(huì)出現(xiàn)數(shù)據(jù)不一致問(wèn)題。在其他場(chǎng)合,你需要使用鎖、synchronized關(guān)鍵字或者其他同步方法。在Java5中,并發(fā)API中有一種新的變量,叫作原子變量。這些變量都是在單個(gè)變量上支持原子操作的類。它們含有一個(gè)名為compareAndSet(oldValue,newValue)的方法,該方法具有一種機(jī)制,可用于探測(cè)某個(gè)步驟中將新值賦給變量的操作是否完成。如果變量的值等于oldValue,那么該方法將變量的值更改為newValue并且返回true。否則,該方法返回false。以類似方式工作的方法還有很多,例如getAndIncrement()和getAndDecrement()等。這些方法也都是原子的。該解決方案是免鎖的,也就是說(shuō)不需要使用鎖或者任何同步機(jī)制,因此它的性能比任何采用同步機(jī)制的解決方案要好。在Java中可用的最重要的原子變量有如下幾種:AtomicIntegerAtomicLongAtomicReferenceAtomicBooleanLongAdderDoubleAdder1.6.11占有鎖的時(shí)間盡可能短和其他所有同步機(jī)制一樣,鎖允許你定義一個(gè)臨界段,一次只有一個(gè)任務(wù)可以執(zhí)行。當(dāng)一個(gè)任務(wù)執(zhí)行該臨界段時(shí),其他要執(zhí)行臨界段的任務(wù)都將被阻塞并且要等待該臨界段被釋放。這樣,該應(yīng)用程序其實(shí)是以串行方式來(lái)工作的。你要特別注意臨界段中的指令,因?yàn)槿绻涣私馑脑挄?huì)降低應(yīng)用程序的性能。你必須將臨界段定制得盡可能小,而且它必須僅包含處理與其他任務(wù)共享的數(shù)據(jù)的指令,這樣應(yīng)用程序花費(fèi)在串行處理上的時(shí)間就會(huì)最少。避免在臨界段中執(zhí)行你無(wú)法控制的代碼。例如,你寫(xiě)了一個(gè)庫(kù),它接收一個(gè)用戶自定義的Callable對(duì)象作為參數(shù),但是該對(duì)象有時(shí)候需要由你啟動(dòng),而你并不知道該Callable對(duì)象中到底有什么。也許它會(huì)阻塞輸入/輸出、獲取某些鎖、調(diào)用你庫(kù)中的其他方法,或者只是需要處理很長(zhǎng)一段時(shí)間。因此,如果可能的話,在你的庫(kù)并不占有任何鎖時(shí),再嘗試執(zhí)行這些代碼。如果對(duì)你的算法來(lái)說(shuō)不可能做到這一點(diǎn),就在該庫(kù)的文檔中說(shuō)明這一情況,并且盡可能說(shuō)明對(duì)用戶提供的代碼的限制(例如,這些代碼不應(yīng)該加任何鎖)。一個(gè)很好的例子就是ConcurrentHashMap類的compute()方法的文檔說(shuō)明。1.6.12謹(jǐn)慎使用延遲初始化延遲初始化就是將對(duì)象的創(chuàng)建延遲到該對(duì)象在應(yīng)用程序中首次使用時(shí)的一種機(jī)制。它的主要優(yōu)點(diǎn)是可以使內(nèi)存使用最小化,因?yàn)槟阒恍枰獎(jiǎng)?chuàng)建實(shí)際需要的對(duì)象。但是在并發(fā)應(yīng)用程序中它也可能引發(fā)問(wèn)題。如果你使用某個(gè)方法初始化某一對(duì)象,并且該方法同時(shí)被兩個(gè)不同的任務(wù)調(diào)用,那么你可以初始化兩個(gè)不同的對(duì)象。但是這可能會(huì)帶來(lái)問(wèn)題(例如對(duì)單例模式的類來(lái)說(shuō)),因?yàn)槟阒幌霝檫@些類創(chuàng)建一個(gè)對(duì)象。這一問(wèn)題已經(jīng)有了很好的解決方案,這就是延遲加載的單例模式(請(qǐng)查看維基百科中關(guān)于“initialization-on-demandholderidiom”的解釋)。1.6.13避免在臨界段中使用阻塞操作阻塞操作是指阻塞任務(wù)對(duì)其進(jìn)行調(diào)用,直到某一事件發(fā)生后再調(diào)用的操作。例如,當(dāng)你從某一文件讀取數(shù)據(jù)或者向控制臺(tái)輸出數(shù)據(jù)時(shí),調(diào)用這些操作的任務(wù)必須等待,直到這些操作完成為止。如果臨界段中包含了這樣的操作,應(yīng)用程序的性能就會(huì)降低,因?yàn)樾枰獔?zhí)行該臨界段的任務(wù)都無(wú)法執(zhí)行臨界段了。位于臨界段中的操作等待某個(gè)I/O操作結(jié)束,而其他任務(wù)則一直在等待臨界段。除非必要,否則不要在臨界段中加入阻塞操作。1.7小結(jié)并發(fā)程序設(shè)計(jì)包含了在一臺(tái)計(jì)算機(jī)上同時(shí)運(yùn)行多個(gè)任務(wù)或者進(jìn)程所必需的工具和技術(shù),以及在它們之間確保不出現(xiàn)數(shù)據(jù)丟失和不一致所需的通信和同步。本章一開(kāi)始介紹了并發(fā)的基本概念。要完全理解本書(shū)中的例子,你必須知道并理解并發(fā)、并行和同步等術(shù)語(yǔ)。然而,并發(fā)處理也會(huì)產(chǎn)生一些問(wèn)題,例如數(shù)據(jù)競(jìng)爭(zhēng)條件、死鎖、活鎖等。你還必須知道并發(fā)應(yīng)用程序可能存在的問(wèn)題,這會(huì)幫助你識(shí)別和解決這些問(wèn)題。我們還介紹了Intel公司提出的一個(gè)五步驟的簡(jiǎn)單方法論,它用于將一個(gè)串行算法轉(zhuǎn)換成并發(fā)算法。此外還展示了采用Java語(yǔ)言實(shí)現(xiàn)的一些并發(fā)設(shè)計(jì)模式,介紹了一些在實(shí)現(xiàn)并發(fā)應(yīng)用程序時(shí)可以借鑒的技巧。最后簡(jiǎn)單介紹了Java并發(fā)API的組件。這是一個(gè)非常豐富的API,既有低層機(jī)制,也有很高層的機(jī)制,讓你很容易實(shí)現(xiàn)強(qiáng)大的并發(fā)應(yīng)用程序。下一章,你將學(xué)習(xí)如何使用Java并發(fā)應(yīng)用程序的基本要素:Thread類和Runnable接口。第2章使用基本元素:Thread和Runnable執(zhí)行線程是并發(fā)應(yīng)用程序的核心。實(shí)現(xiàn)并發(fā)應(yīng)用程序時(shí),無(wú)論采用何種編程語(yǔ)言,都必須創(chuàng)建不同的執(zhí)行線程,并且這些線程以不確定的順序并行運(yùn)行,除非你使用同步元素,比如信號(hào)量。在Java中,創(chuàng)建執(zhí)行線程有兩種方法。擴(kuò)展Thread類。實(shí)現(xiàn)Runnable接口。本章將介紹在Java中使用這些元素實(shí)現(xiàn)并發(fā)應(yīng)用程序的方法,主要內(nèi)容如下。Java中的線程:特征和狀態(tài)。Thread類和Runnable接口。第一個(gè)例子:矩陣乘法。第二個(gè)例子:文件搜索。2.1Java中的線程如今,計(jì)算機(jī)用戶(以及移動(dòng)終端和平板電腦用戶)使用電腦工作時(shí)要同時(shí)使用不同的應(yīng)用程序。閱讀新聞、在社交網(wǎng)絡(luò)上發(fā)表文章或聽(tīng)音樂(lè)的同時(shí),可以使用文字處理程序編寫(xiě)文檔。之所以可以同時(shí)做以上所有事情,是因?yàn)楝F(xiàn)代操作系統(tǒng)支持多進(jìn)程處理。用戶可以同時(shí)執(zhí)行不同的任務(wù)。此外,在應(yīng)用程序內(nèi)部,你也可以同時(shí)做不同的事情。例如,如果你正在使用文字處理程序,在為文本添加粗體樣式的同時(shí)便可保存文件。這是因?yàn)橛糜诰帉?xiě)這些應(yīng)用程序的現(xiàn)代編程語(yǔ)言允許程序員在應(yīng)用程序中創(chuàng)建多個(gè)執(zhí)行線程。每個(gè)執(zhí)行線程執(zhí)行不同的任務(wù),這樣你就可以同時(shí)做不同的事情。Java使用Thread類實(shí)現(xiàn)執(zhí)行線程。你可以使用以下機(jī)制在應(yīng)用程序中創(chuàng)建執(zhí)行線程。擴(kuò)展Thread類并重載run()方法。實(shí)現(xiàn)Runnable接口,并將該類的對(duì)象傳遞給Thread對(duì)象的構(gòu)造函數(shù)。這兩種情況下你都會(huì)得到一個(gè)Thread對(duì)象,但是相對(duì)于第一種方式來(lái)說(shuō),更推薦使用第二種。其主要優(yōu)勢(shì)如下。Runnable是一個(gè)接口:你可以實(shí)現(xiàn)其他接口并擴(kuò)展其他類。對(duì)于采用Thread類的方式,你只能擴(kuò)展這一個(gè)類??梢酝ㄟ^(guò)線程來(lái)執(zhí)行Runnable對(duì)象,但也可以通過(guò)其他類似執(zhí)行器的Java并發(fā)對(duì)象來(lái)執(zhí)行。這樣可以更靈活地更改并發(fā)應(yīng)用程序??梢酝ㄟ^(guò)不同線程使用同一Runnable對(duì)象。一旦有了Thread對(duì)象,就必須使用start()方法創(chuàng)建新的執(zhí)行線程并且執(zhí)行Thread類的run()方法。如果直接調(diào)用run()方法,那么你將調(diào)用常規(guī)Java方法而不會(huì)創(chuàng)建新的執(zhí)行線程。下面來(lái)看看Java編程語(yǔ)言中線程最重要的特征。2.1.1Java中的線程:特征和狀態(tài)關(guān)于Java的線程,首先要說(shuō)明的是,所有的Java程序,不論并發(fā)與否,都有一個(gè)名為主線程的Thread對(duì)象。你可能知道,JavaSE程序通過(guò)main()方法啟動(dòng)執(zhí)行過(guò)程。執(zhí)行該程序時(shí),Java虛擬機(jī)(JVM)將創(chuàng)建一個(gè)新Thread并在該線程中執(zhí)行main()方法。這是非并發(fā)應(yīng)用程序中唯一的線程,也是并發(fā)應(yīng)用程序中的第一個(gè)線程。與其他編程語(yǔ)言相同,Java中的線程共享應(yīng)用程序中的所有資源,包括內(nèi)存和打開(kāi)的文件。這是一個(gè)強(qiáng)大的工具,因?yàn)樗鼈兛梢钥焖俣?jiǎn)單地共享信息。但是,正如第1章所述,必須使用足夠的同步元素避免數(shù)據(jù)競(jìng)爭(zhēng)條件。Java中的所有線程都有一個(gè)優(yōu)先級(jí),這個(gè)整數(shù)值介于Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之間(實(shí)際上它們的值分別是1和10)。所有線程在創(chuàng)建時(shí)其默認(rèn)優(yōu)先級(jí)都是Thread.NORM_PRIORITY(實(shí)際上它的值是5)??梢允褂胹etPriority()方法更改Thread對(duì)象的優(yōu)先級(jí)(如果該操作不允許執(zhí)行,它會(huì)拋出SecurityException異常)和getPriority()方法獲得Thread對(duì)象的優(yōu)先級(jí)。對(duì)于Java虛擬機(jī)和線程首選底層操作系統(tǒng)來(lái)說(shuō),這種優(yōu)先級(jí)是一種提示,而非一種契約。線程的執(zhí)行順序并沒(méi)有保證。通常,較高優(yōu)先級(jí)的線程將在較低優(yōu)先級(jí)的線程之前執(zhí)行,但是,正如之前所述,這一點(diǎn)并不能保證。在Java中,可以創(chuàng)建兩種線程。守護(hù)線程。非守護(hù)線程。二者之間的區(qū)別在于它們?nèi)绾斡绊懗绦虻慕Y(jié)束。當(dāng)有下列情形之一時(shí),Java程序?qū)⒔Y(jié)束其執(zhí)行過(guò)程。程序執(zhí)行Runtime類的exit()方法,而且用戶有權(quán)執(zhí)行該方法。應(yīng)用程序的所有非守護(hù)線程均已結(jié)束執(zhí)行,無(wú)論是否有正在運(yùn)行的守護(hù)線程。具有這些特征的守護(hù)線程通常用在作為垃圾收集器或緩存管理器的應(yīng)用程序中,執(zhí)行輔助任務(wù)。你可以使用isDaemon()方法檢查線程是否為守護(hù)線程,也可以使用setDaemon()方法將某個(gè)線程確立為守護(hù)線程。要注意,必須在線程使用start()方法開(kāi)始執(zhí)行之前調(diào)用此方法。最后,不同情況下線程的狀態(tài)不同。所有可能的狀態(tài)都在Thread.States類中定義。你可以使用getState()方法獲取Thread對(duì)象的狀態(tài)。顯然,你還可以直接更改線程的狀態(tài)。線程的可能狀態(tài)如下。NEW:Thread對(duì)象已經(jīng)創(chuàng)建,但是還沒(méi)有開(kāi)始執(zhí)行。RUNNABLE:Thread對(duì)象正在Java虛擬機(jī)中運(yùn)行。BLOCKED:Thread對(duì)象正在等待鎖定。WAITING:Thread對(duì)象正在等待另一個(gè)線程的動(dòng)作。TIME_WAITING:Thread對(duì)象正在等待另一個(gè)線程的操作,但是有時(shí)間限制。THREAD:Thread對(duì)象已經(jīng)完成了執(zhí)行。在給定時(shí)間內(nèi),線程只能處于一個(gè)狀態(tài)。這些狀態(tài)不能映射到操作系統(tǒng)的線程狀態(tài),它們是JVM使用的狀態(tài)。了解了Java編程語(yǔ)言中最重要的線程特性之后,讓我們來(lái)看看Runnable接口和Thread類最重要的方法。2.1.2Thread類和Runnable接口如前文所述,你可以使用以下任一機(jī)制創(chuàng)建新的執(zhí)行線程。擴(kuò)展Thread類并且重載其run()方法。實(shí)現(xiàn)Runnable接口,并將該對(duì)象的實(shí)例傳遞給Thread對(duì)象的構(gòu)造函數(shù)。在好的Java實(shí)踐做法中,相對(duì)于第一種方法而言,更推薦使用第二種方法,這將是我們?cè)诒菊乱约罢緯?shū)中都將采用的方法。Runnable接口只定義了一種方法:run()方法。這是每個(gè)線程的主方法。當(dāng)你執(zhí)行start()方法來(lái)啟動(dòng)一個(gè)新線程時(shí),它將調(diào)用run()方法(Thread類的run()方法或者在Thread類的構(gòu)造函數(shù)中以參數(shù)形式傳遞的Runnable對(duì)象)。相反,Thread類有很多不同的方法。它有一種run()方法,

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論