使用Thread類別建立執(zhí)行緒課件_第1頁
使用Thread類別建立執(zhí)行緒課件_第2頁
使用Thread類別建立執(zhí)行緒課件_第3頁
使用Thread類別建立執(zhí)行緒課件_第4頁
使用Thread類別建立執(zhí)行緒課件_第5頁
已閱讀5頁,還剩223頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

第15章多執(zhí)行緒(Multithreading)1第15章多執(zhí)行緒(Multithreading)1本章提要15-1甚麼是執(zhí)行緒?15-2執(zhí)行緒的同步(Synchronization)15-3執(zhí)行緒間的協(xié)調(diào)15-4綜合演練2本章提要15-1甚麼是執(zhí)行緒?2前言到目前為止,我們所撰寫的程式流程都是從頭執(zhí)行到尾,同一時間只會進(jìn)行一件事??墒窃趯嶋H撰寫程式的時候,常常會遇到同一時間希望能夠進(jìn)行多件事情的狀況。舉例來說,在前幾章曾經(jīng)撰寫過一個碼錶程式,細(xì)心思考的讀者可能已經(jīng)發(fā)現(xiàn)到,這個碼錶程式其實一點用處都沒有。由於同一時間只能做一件事,因此碼錶程式除了倒數(shù)計時以外,甚麼事也不能做。3前言到目前為止,我們所撰寫的程式流程都是從頭執(zhí)行到尾,同前言我們所需要的是倒數(shù)的同時,另一邊又可以進(jìn)行其它工作的碼錶。這在現(xiàn)實生活中也很常見,像是最簡單的泡麵來說,將麵擺好,加入沸水後,可以準(zhǔn)備個倒數(shù)的鬧鐘,定好三分鐘後響。這樣一來,就可以看看書,等到鬧鐘響了,就可以享用熱騰騰的麵了。這泡麵、倒數(shù)、看書就是三件同時進(jìn)行的工作,如果同一時間只能進(jìn)行一件事,那可就麻煩了。4前言我們所需要的是倒數(shù)的同時,另一邊又可以進(jìn)行其它工作的碼前言Java就提供這種同時進(jìn)行多項工作的能力,稱為多執(zhí)行緒(Multithreading)。由於具有此項能力,使得撰寫Java程式時增加了許多的彈性,也使得程式撰寫起來更加直覺。5前言Java就提供這種同時進(jìn)行多項工作的能力,稱為多執(zhí)行15-1甚麼是執(zhí)行緒?要知道甚麼是執(zhí)行緒,其實並不難。想像一下一家製造汽車的工廠,為了達(dá)到最高的效率,工廠都會以生產(chǎn)線的方式,將汽車相互獨立的元件分開且同時製造。由於製造車身和製造引擎並不需要互相等待,因此可以有一條生產(chǎn)線製造車身、一條生產(chǎn)線製造引擎。這樣一來,車身和引擎就可能同時完成,馬上就可以進(jìn)行組裝。否則的話,如果車身要等引擎製造完成才能動工,那麼整臺車製造完成的時間就會拖長了。615-1甚麼是執(zhí)行緒?要知道甚麼是執(zhí)行緒,其實並不難。想甚麼是執(zhí)行緒?如果將程式對比為製造汽車的工廠,那麼執(zhí)行緒就是工廠中的每一條生產(chǎn)線,可以和其他的執(zhí)行緒同時進(jìn)行手上的工作。也就是說,每一個執(zhí)行緒有它自己的流程,當(dāng)程式執(zhí)行時,每一個執(zhí)行緒便依據(jù)自己的流程進(jìn)行,同時處理各自的工作。7甚麼是執(zhí)行緒?如果將程式對比為製造汽車的工廠,那麼執(zhí)行緒就甚麼是執(zhí)行緒?8甚麼是執(zhí)行緒?8使用Thread類別建立執(zhí)行緒接下來我們就實際建立一個多執(zhí)行緒的程式,讓您可以觀察程式的執(zhí)行結(jié)果,以確實瞭解執(zhí)行緒的含意。在Java中,每一個執(zhí)行緒都是以一個Thread物件來表示,要建立新的執(zhí)行緒,最簡單的方法就是從Thread類別(屬於java.lang套件)衍生新的類別,並且重新定義Thread()類別中的run()方法,進(jìn)行這個新執(zhí)行緒所要負(fù)責(zé)的工作。例如:9使用Thread類別建立執(zhí)行緒接下來我們就實際建立一個多使用Thread類別建立執(zhí)行緒10使用Thread類別建立執(zhí)行緒10使用Thread類別建立執(zhí)行緒11使用Thread類別建立執(zhí)行緒11使用Thread類別建立執(zhí)行緒12使用Thread類別建立執(zhí)行緒12使用Thread類別建立執(zhí)行緒在第3行中,定義了一個Thread的子類別TimerThread,並且重新定義了run()方法,這個方法的內(nèi)容只是不斷的取得目前的時間,然後顯示在螢?zāi)簧?。這裡有兩件事需要注意:13使用Thread類別建立執(zhí)行緒在第3行中,定義了一使用Thread類別建立執(zhí)行緒取得時間的方法是產(chǎn)生一個java.util套件中的Date物件,這個物件的建構(gòu)方法會取得目前的時間,記錄下來。Date類別重新定義了toString()方法,可以將其記錄的日期時間以特定格式轉(zhuǎn)成字串。相關(guān)的說明請參考JDK文件。第6行的for迴圈是故意用來減緩程式顯示訊息的速度,避免不斷迅速地執(zhí)行第8行在螢?zāi)簧巷@示訊息,而無法閱讀結(jié)果。14使用Thread類別建立執(zhí)行緒取得時間的方法是產(chǎn)生一個使用Thread類別建立執(zhí)行緒在main()方法中,就建立了一個TimerThread物件,然後呼叫其start()方法。start()是繼承自Thread的方法,執(zhí)行後,就會建立一個新的執(zhí)行緒,然後在這個新的執(zhí)行緒中呼叫run()方法。從此開始,run()方法的執(zhí)行就和原本程式的流程分開,同時執(zhí)行。也就是說,新的執(zhí)行緒就從第5行開始執(zhí)行,而同時原本的程式流程則會從start()中返回,由第18行接續(xù)執(zhí)行。15使用Thread類別建立執(zhí)行緒在main()方法中使用Thread類別建立執(zhí)行緒main()方法中接下來的內(nèi)容就和TimerThread類別的run()方法相似,只是顯示的訊息開頭不同而已。由於main()方法與run()方法中各是兩個無窮迴圈,所以兩個執(zhí)行緒就不斷的顯示目前的日期時間。如果要結(jié)束程式,必須按下[Ctrl]+[C]鍵強(qiáng)迫終結(jié)。16使用Thread類別建立執(zhí)行緒main()方法中接下使用Thread類別建立執(zhí)行緒您可以從執(zhí)行結(jié)果中看出來,新執(zhí)行緒與原本的流程是交錯執(zhí)行的,剛開始新執(zhí)行緒先顯示訊息,然後舊流程插入,如此反覆執(zhí)行。如果再重新執(zhí)行程式,結(jié)果並不會完全相同,但仍然是新執(zhí)行緒與原始流程交錯執(zhí)行,而這也是多執(zhí)行緒最重要的一點。17使用Thread類別建立執(zhí)行緒您可以從執(zhí)行結(jié)果中看出來,使用Runnable介面建立執(zhí)行緒由於Java並不提供多重繼承,如果類別需要繼承其他類別,就沒有辦法再繼承Thread類別來建立執(zhí)行緒。對於這種狀況,Java提供有Ruunable介面,讓任何類別都可以用來建立執(zhí)行緒。請看以下的範(fàn)例:18使用Runnable介面建立執(zhí)行緒由於Java並不提使用Runnable介面建立執(zhí)行緒19使用Runnable介面建立執(zhí)行緒19使用Runnable介面建立執(zhí)行緒20使用Runnable介面建立執(zhí)行緒20使用Runnable介面建立執(zhí)行緒要透過Runnable介面建立執(zhí)行緒,第一步就是定義一個實作Runnable介面的類別,並重新定義run()方法。接著再使用需要單一參數(shù)的建構(gòu)方法來產(chǎn)生Thread物件,並且將實作有Runnable介面的物件傳給建構(gòu)方法。產(chǎn)生Thread物件之後,只要呼叫其start()方法就可以啟動新的執(zhí)行緒。21使用Runnable介面建立執(zhí)行緒要透過Runnabl執(zhí)行緒的各種狀態(tài)執(zhí)行緒除了能不斷的執(zhí)行以外,還可以依據(jù)需求切換到不同的狀態(tài)。舉例來說,在上一小節(jié)的範(fàn)例中,使用了一個並不進(jìn)行任何實質(zhì)動作的迴圈來延遲下一次顯示訊息的時間,相同的工作可以改成讓執(zhí)行緒進(jìn)入睡眠狀態(tài)一段時間,然後在時間到後繼續(xù)執(zhí)行:22執(zhí)行緒的各種狀態(tài)執(zhí)行緒除了能不斷的執(zhí)行以外,還可以依據(jù)需求執(zhí)行緒的各種狀態(tài)23執(zhí)行緒的各種狀態(tài)23執(zhí)行緒的各種狀態(tài)24執(zhí)行緒的各種狀態(tài)24執(zhí)行緒的各種狀態(tài)其中第7行就是呼叫Thread類別所定義的static方法

sleep(),讓新的執(zhí)行緒進(jìn)入睡眠狀態(tài)。sleep()方法的參數(shù)表示睡眠的時間,以毫秒(即1/1000秒)為單位,因此傳入1000就等於是1秒。要注意的是,呼叫sleep()方法可能會引發(fā)java.lang.InterruptedException例外,所以必須使用try...catch敘述攔截例外。25執(zhí)行緒的各種狀態(tài)其中第7行就是呼叫Thread類別所執(zhí)行緒的各種狀態(tài)相同的道理,原始的流程也一樣可以透過進(jìn)入睡眠狀態(tài)的方式,暫停執(zhí)行一段時間。sleep()方法會讓目前在執(zhí)行中的執(zhí)行緒進(jìn)入睡眠狀態(tài),因此第23行一樣呼叫sleep()睡眠1秒鐘。當(dāng)執(zhí)行緒進(jìn)入睡眠狀態(tài)時,其他的執(zhí)行緒仍然會繼續(xù)執(zhí)行,不會受到影響。26執(zhí)行緒的各種狀態(tài)相同的道理,原始的流程也一樣可以透過進(jìn)入睡除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況預(yù)備狀態(tài)(Ready):這個狀態(tài)表示執(zhí)行緒將排隊等待執(zhí)行,當(dāng)建立執(zhí)行緒物件並執(zhí)行start()方法後,就會進(jìn)入這個狀態(tài)。相同的道理,當(dāng)執(zhí)行緒結(jié)束睡眠狀態(tài)後,也會進(jìn)入此狀態(tài)等待執(zhí)行。執(zhí)行狀態(tài)(Running):這個狀態(tài)表示此執(zhí)行緒正在執(zhí)行,您可以呼叫Thread類別所定義的static方法currentThread(),取得代表目前流程的執(zhí)行緒的Thread物件。27除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況預(yù)備狀態(tài)除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況凍結(jié)狀態(tài)(Blocked):當(dāng)執(zhí)行緒執(zhí)行特定的處理,像是從磁碟讀取資料時,就會進(jìn)入凍結(jié)狀態(tài)。等到處理完畢,就會結(jié)束凍結(jié)狀態(tài),進(jìn)入預(yù)備狀態(tài)。另外,在下一節(jié)中也會介紹執(zhí)行緒的同步,因為sychronized區(qū)塊或是sychronized方法而等待其他執(zhí)行緒時,也會進(jìn)入凍結(jié)狀態(tài),詳細(xì)內(nèi)容請看15-2節(jié)。28除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況凍結(jié)狀態(tài)除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況等待狀態(tài)(Waiting):當(dāng)執(zhí)行緒呼叫Object類別所定義的wait()方法自願等待時,就會進(jìn)入等待狀態(tài),一直到其他執(zhí)行緒呼叫notify()或是notifyAll()方法解除其等待狀態(tài),才會再進(jìn)入預(yù)備狀態(tài)。有關(guān)wait()與notify()、notifyAll()方法等,請看15-2節(jié)。29除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況等待狀態(tài)除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況30除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況3015-2執(zhí)行緒的同步(Synchronization)由於多個執(zhí)行緒是輪流執(zhí)行的,因此就可能發(fā)生某個執(zhí)行緒正在進(jìn)行一項工作的中途,因為分配的時間結(jié)束而暫停執(zhí)行,由另一個執(zhí)行緒執(zhí)行,等下次輪到它執(zhí)行時,才接續(xù)進(jìn)行之前的工作。3115-2執(zhí)行緒的同步(Synchronization)由執(zhí)行緒的同步(Synchronization)在這樣的情況下,如果多個執(zhí)行緒都會使用到同一個資源。比如說都會先取出某個變數(shù)值,進(jìn)行特定的運算之後修改該變數(shù),那麼就可能會造成執(zhí)行緒A先取出變數(shù)值,計算後還未修改變數(shù)值時輪到執(zhí)行緒B執(zhí)行,執(zhí)行緒B取得未修改的值,計算後修改變數(shù)值,等到再輪回執(zhí)行緒A時,便將之前計算的值放回變數(shù),剛剛執(zhí)行緒B的計算結(jié)果便不見了,使得計算結(jié)果不正確。32執(zhí)行緒的同步(Synchronization)在這樣的情況多執(zhí)行緒存取共用資源的問題請看看以下的範(fàn)例,就可以瞭解上述的狀況。假設(shè)總統(tǒng)大選時某候選人派出多個人進(jìn)駐各投票所蒐集得票數(shù),每間隔一段時間每個投票所會傳回該段時間新增的得票數(shù),並加總得出總得票數(shù)。如果使用個別的執(zhí)行緒來蒐集各投票所的得票,程式大概如下:33多執(zhí)行緒存取共用資源的問題請看看以下的範(fàn)例,就可以瞭解上述多執(zhí)行緒存取共用資源的問題34多執(zhí)行緒存取共用資源的問題34多執(zhí)行緒存取共用資源的問題35多執(zhí)行緒存取共用資源的問題35多執(zhí)行緒存取共用資源的問題36多執(zhí)行緒存取共用資源的問題36多執(zhí)行緒存取共用資源的問題37多執(zhí)行緒存取共用資源的問題37多執(zhí)行緒存取共用資源的問題38多執(zhí)行緒存取共用資源的問題38多執(zhí)行緒存取共用資源的問題PollingStation類別就代表了個別的投票所,其中name成員紀(jì)錄此投票所的名稱,reportTimes指的是投票所應(yīng)該回報新增得票數(shù)的次數(shù);而total則記錄此投票所目前的總票數(shù),v成員則記錄了要統(tǒng)計總票數(shù)的Vote物件。39多執(zhí)行緒存取共用資源的問題PollingStation類別多執(zhí)行緒存取共用資源的問題run()方法是執(zhí)行緒的主體,使用了一個迴圈回報新增得票數(shù)。我們使用了java.lang.Math類別所提供的static方法random()來產(chǎn)生一個介於0到1之間的double數(shù)值,將之乘上500後可以得到一個介於0~500的新增得票數(shù)。接著,就呼叫Vote類別的reportCount()方法回報新增的得票數(shù)。最後,再將新增得票數(shù)加入此投票所的總投票數(shù)。40多執(zhí)行緒存取共用資源的問題run()方法是執(zhí)行緒的主體,多執(zhí)行緒存取共用資源的問題在Vote類別中,total成員記錄總得票數(shù),而numOfStations則是投票所的數(shù)量,stations則是記錄所有PollingStation物件的陣列。reportCount()方法是提供給PollingStation物件回報新增投票數(shù)用,它會先顯示此投票所名稱以及新增的得票數(shù),然後再顯示總得票數(shù)。41多執(zhí)行緒存取共用資源的問題在Vote類別中,total多執(zhí)行緒存取共用資源的問題在startReport()方法中,一開始是一一產(chǎn)生代表投票所的物件,然後再啟動各個執(zhí)行緒。接著,就等待各個投票所開票完畢,等待的方法是呼叫Thread類別的join()方法,這個方法會等到對應(yīng)此Thread物件的執(zhí)行緒執(zhí)行完畢後才返回。42多執(zhí)行緒存取共用資源的問題在startReport()方多執(zhí)行緒存取共用資源的問題因此47~51行的迴圈結(jié)束時,就表示各開票所的執(zhí)行緒都已經(jīng)執(zhí)行完畢。join()方法也和sleep()方法一樣可能會引發(fā)InterruptedExeption例外,所以必須套用try...catch敘述。最後,將各開票所的總票數(shù)及總得票數(shù)顯示出來。43多執(zhí)行緒存取共用資源的問題因此47~51行多執(zhí)行緒存取共用資源的問題main()方法的內(nèi)容很簡單,只是產(chǎn)生1個Vote物件,然後呼叫其startReport()方法而已。44多執(zhí)行緒存取共用資源的問題main()方法的內(nèi)容很簡單,多執(zhí)行緒存取共用資源的問題45多執(zhí)行緒存取共用資源的問題45多執(zhí)行緒存取共用資源的問題從執(zhí)行結(jié)果的最後面就可以看到,明明兩個開票所總得票數(shù)分別是1529以及1034,為什麼總得票數(shù)卻是2072?原因很簡單,就出在reportCount()方法中。由於多個執(zhí)行緒是輪流執(zhí)行,因此當(dāng)某個執(zhí)行緒要將計算好的總票數(shù)放回total變數(shù)前,可能會中斷執(zhí)行。此時換其他的執(zhí)行緒執(zhí)行,也得到新增的得票數(shù),並計算新的總票數(shù)放入total中。46多執(zhí)行緒存取共用資源的問題從執(zhí)行結(jié)果的最後面就可以看到,明多執(zhí)行緒存取共用資源的問題而當(dāng)再輪回原本的執(zhí)行緒時,便將之前計算的總票數(shù)放入total變數(shù)中,導(dǎo)致剛剛由別的執(zhí)行緒所計算的總票數(shù)被蓋掉,計算出錯誤的結(jié)果。以下列出整個執(zhí)行的順序,就可以看到問題出在哪裡了:1號開票所開出423、337、278張票,所以總票數(shù)1038。47多執(zhí)行緒存取共用資源的問題而當(dāng)再輪回原本的執(zhí)行緒時,便將之多執(zhí)行緒存取共用資源的問題1號開票所再開出406張票,計算出總票數(shù)1444後,還未存入total變數(shù)中,便換成2號開票所執(zhí)行。2號開票所便以目前total的值1038為總票數(shù),繼續(xù)開出101、150、112張票,所以總數(shù)變成1401。接著2號開票所開出182張票,計算出總票數(shù)1583後,未放回total變數(shù)便換成1號開票所開票。48多執(zhí)行緒存取共用資源的問題1號開票所再開出406張票,多執(zhí)行緒存取共用資源的問題此時1號開票所將第2步中計算出的1444存回total,蓋掉了第3步中的結(jié)果。然後再開出85張票,所以總數(shù)變成1529。又再換成2號開票所,把第4步中計算的結(jié)果1583存回total,蓋掉了第5步中的結(jié)果。最後,再開出489張票,結(jié)果就變成總票數(shù)2072了。49多執(zhí)行緒存取共用資源的問題此時1號開票所將第2步中計多執(zhí)行緒存取共用資源的問題您可以看到,由於在第6步中蓋掉了total的內(nèi)容,等於是漏算了第2步中的406張票與第5步中的85張票了,因此總票數(shù)2072與真正的總票數(shù)1529+1034=2563差了491張票??上攵?如果真的計票軟體這樣設(shè)計的話,候選人可都要跳腳了。50多執(zhí)行緒存取共用資源的問題您可以看到,由於在第6步中蓋使用synchronized區(qū)塊要解決上述的問題,必須要有一種方式,可以確保計算總票數(shù)以及將總票數(shù)放回total變數(shù)的過程中不會被中斷,這樣才能避免將總票數(shù)放回total變數(shù)時不會蓋掉正確的結(jié)果。51使用synchronized區(qū)塊要解決上述的問題,必須使用synchronized標(biāo)示方法在Java中,就提供有synchronized字符,可以讓您標(biāo)示一個同一時間僅能有一個執(zhí)行緒執(zhí)行的方法。只要將原來的reportCount()方法加上synchronized字符,程式就不會出錯了:52使用synchronized標(biāo)示方法在Java中,使用synchronized標(biāo)示方法53使用synchronized標(biāo)示方法53使用synchronized標(biāo)示方法加上synchronized字符後,只要有執(zhí)行緒正在執(zhí)行此方法,其他的執(zhí)行緒要想執(zhí)行同一個方法時,就會被強(qiáng)制暫停,等到目前的執(zhí)行緒執(zhí)行完此方法後才能繼續(xù)執(zhí)行。如此一來,就可以避免在存回總票數(shù)之前被中斷的情況,保證total的值一定會正確了。以下就是正確的執(zhí)行結(jié)果:54使用synchronized標(biāo)示方法加上synchro使用synchronized標(biāo)示方法55使用synchronized標(biāo)示方法55使用synchronized標(biāo)示程式區(qū)塊有的時候,只有方法中的一段程式需要保證同一時間僅有單一執(zhí)行緒可以執(zhí)行,這時您也可以只標(biāo)示需要保證完整執(zhí)行的程式區(qū)塊,而不必標(biāo)示整個方法。以我們的範(fàn)例來說,真正需要保證完整執(zhí)行的是取得total變數(shù)值到加總完存回total變數(shù)的這一段,因此程式可以改寫為這樣:56使用synchronized標(biāo)示程式區(qū)塊有的時候,只有使用synchronized標(biāo)示程式區(qū)塊57使用synchronized標(biāo)示程式區(qū)塊57使用synchronized標(biāo)示程式區(qū)塊其中就使用了synchronized來標(biāo)示第30~35行的內(nèi)容。要注意的是,此種標(biāo)示方法必須在synchronized字符之後指出執(zhí)行緒間所共用而造成問題的資源,這必須是一個指向物件的參照值。以我們的範(fàn)例來說,由於是多個執(zhí)行緒都去修改total變數(shù)而造成問題,但total變數(shù)並非是參照值,因此我們就以包含total變數(shù)的Vote物件本身作為共享的資源,來使用synchronized敘述。58使用synchronized標(biāo)示程式區(qū)塊其中就使用了s使用synchronized標(biāo)示程式區(qū)塊這樣一來,Java就會知道以下這個區(qū)塊內(nèi)的程式會因為多個執(zhí)行緒同時使用到指定的資源而造成問題,因而幫我們控制同一時間僅能有一個執(zhí)行緒執(zhí)行以下區(qū)塊。如果有其他執(zhí)行緒也想進(jìn)入此區(qū)塊,就會被強(qiáng)制暫停,等待目前執(zhí)行此區(qū)塊的執(zhí)行緒離開此區(qū)塊,才能繼續(xù)執(zhí)行。59使用synchronized標(biāo)示程式區(qū)塊這樣一來,Ja15-3執(zhí)行緒間的協(xié)調(diào)使用多執(zhí)行緒時,經(jīng)常會遇到的一種狀況,就是執(zhí)行緒A在等執(zhí)行緒B完成某項工作,而當(dāng)執(zhí)行緒B完成後,執(zhí)行緒B又要等執(zhí)行緒A消化執(zhí)行緒B剛剛完成的工作,如此反覆進(jìn)行。這時候,就需要一種機(jī)制,可以讓執(zhí)行緒之間互相協(xié)調(diào),彼此可以知道對方的進(jìn)展。6015-3執(zhí)行緒間的協(xié)調(diào)使用多執(zhí)行緒時,經(jīng)常會遇到的一種狀執(zhí)行緒間相互合作的問題如果上一節(jié)的選票統(tǒng)計是由派駐各開票所的人員打電話回來給候選人的選舉總部,而總部僅有一名助理負(fù)責(zé)接聽電話,當(dāng)助理接聽電話時,自然其他開票所的人員就沒辦法打進(jìn)來。助理接聽電話時,必須負(fù)責(zé)將新增得票數(shù)記錄下來,然後通知選舉總部負(fù)責(zé)人去加總票數(shù)。那麼程式就可能是這樣:61執(zhí)行緒間相互合作的問題如果上一節(jié)的選票統(tǒng)計是由派駐各開票所的執(zhí)行緒間相互合作的問題62執(zhí)行緒間相互合作的問題62執(zhí)行緒間相互合作的問題63執(zhí)行緒間相互合作的問題63執(zhí)行緒間相互合作的問題64執(zhí)行緒間相互合作的問題64執(zhí)行緒間相互合作的問題65執(zhí)行緒間相互合作的問題65執(zhí)行緒間相互合作的問題66執(zhí)行緒間相互合作的問題66執(zhí)行緒間相互合作的問題67執(zhí)行緒間相互合作的問題67執(zhí)行緒間相互合作的問題其中22~35行就是用來表示選舉總部助理的Assistant類別。這個類別只有2個方法,分別是用來讓PollingStation物件呼叫,以回報新增得票數(shù)的reportCount()方法,以及提供給主流程取得新增票數(shù)的getCount()方法。由於這2個方法都必須存取共用的資源count,所以都以synchronized來控制存取count變數(shù)的同步。68執(zhí)行緒間相互合作的問題其中22~35行就是用來表示選執(zhí)行緒間相互合作的問題reportCount()方法中是顯示並且記錄回報的票數(shù),而getCount()方法就很單純的傳回count。PollingStation類別的內(nèi)容和之前的範(fàn)例幾乎一樣,不同的只是原來記錄Vote物件的變數(shù)改成記錄Assistant物件,並且一併改成呼叫Assistant類別的reportCount()方法回報新增票數(shù)。69執(zhí)行緒間相互合作的問題reportCount()方法中是顯執(zhí)行緒間相互合作的問題在main()方法中,先產(chǎn)生代表助理的Assistant物件,然後一一產(chǎn)生各個開票所的PollingStation物件,啟動執(zhí)行緒。然後使用for迴圈依據(jù)開票所的數(shù)量以及個別開票所回報新增票數(shù)的次數(shù)呼叫Assistant的getCount()方法,取得新增票數(shù)加總。最後,顯示各開票所的總票數(shù)以及加總的總票數(shù)。70執(zhí)行緒間相互合作的問題在main()方法中,先產(chǎn)生代表執(zhí)行緒間相互合作的問題71執(zhí)行緒間相互合作的問題71執(zhí)行緒間相互合作的問題最後的結(jié)果明明兩個開票所分別有897及1045票,怎麼總票數(shù)會是1088票呢?仔細(xì)看執(zhí)行結(jié)果會發(fā)現(xiàn),主流程在開票所還沒有回報票數(shù)時,就已經(jīng)先呼叫g(shù)etCount()兩次。更糟的是,總部負(fù)責(zé)人還沒有將記錄下來的新增票數(shù)加總,就又把傳回的新增票數(shù)記錄到count變數(shù)中,蓋掉了之前的數(shù)值,以致於最後的1088票其實是2號開票所最後一次傳回的136票乘上8次的結(jié)果。72執(zhí)行緒間相互合作的問題最後的結(jié)果明明兩個開票所分別有897執(zhí)行緒間相互合作的問題這個程式的問題就出在各個執(zhí)行緒間並沒有協(xié)調(diào)好,助理應(yīng)該告訴總部負(fù)責(zé)人現(xiàn)在沒有資料,請等候;相同的道理,助理也應(yīng)該要知道負(fù)責(zé)人還沒加總好資料,先不要把資料記下來,蓋掉舊的資料。73執(zhí)行緒間相互合作的問題這個程式的問題就出在各個執(zhí)行緒間並沒有協(xié)調(diào)執(zhí)行緒為了解決上述的問題,很顯然的,必須修改Assistant類別,讓它扮演好助理的角色。在Object類別中,提供有一對方法:wait()與notify(),wait()可以讓目前的執(zhí)行緒進(jìn)入等待狀態(tài),直到有別的執(zhí)行緒呼叫同一個物件的notify()方法叫醒,才會繼續(xù)執(zhí)行。因此,我們就可以呼叫Assistant物件的這一對方法來協(xié)調(diào)回報新增票數(shù)與加總票數(shù)的工作:74協(xié)調(diào)執(zhí)行緒為了解決上述的問題,很顯然的,必須修改Ass協(xié)調(diào)執(zhí)行緒75協(xié)調(diào)執(zhí)行緒75協(xié)調(diào)執(zhí)行緒76協(xié)調(diào)執(zhí)行緒76協(xié)調(diào)執(zhí)行緒第24行新增了unprocessedData成員,用來表示是否有新增的票數(shù)還未加總。77協(xié)調(diào)執(zhí)行緒第24行新增了unprocessedData協(xié)調(diào)執(zhí)行緒第29行的while迴圈會在還有新增票數(shù)未加總時讓代表開票所的執(zhí)行緒等待,等待的方式就是呼叫Assistant物件的wait()方法,進(jìn)入等待狀態(tài)。一旦被喚醒繼續(xù)執(zhí)行,就會將新增票數(shù)記錄下來,將unprocessedData設(shè)為true,告訴助理可以加總票數(shù)了。然後呼叫notify(),讓等待加總票數(shù)的主流程可以繼續(xù)執(zhí)行。78協(xié)調(diào)執(zhí)行緒第29行的while迴圈會在還有新增票數(shù)未協(xié)調(diào)執(zhí)行緒第43的while迴圈會在沒有新增票數(shù)需要加總時,讓負(fù)責(zé)加總的主流程等待,等待的方式一樣是呼叫Assistant物件的wait()方法。一旦被喚醒繼續(xù)執(zhí)行時,就會將unprocessedData設(shè)為false,並呼叫Assistant物件的notify()方法,以便喚醒等待回報新增票數(shù)的執(zhí)行緒,讓開票所能夠繼續(xù)回報新增票數(shù)。79協(xié)調(diào)執(zhí)行緒第43的while迴圈會在沒有新增票數(shù)需要協(xié)調(diào)執(zhí)行緒如此一來,執(zhí)行結(jié)果就完全正確了:80協(xié)調(diào)執(zhí)行緒如此一來,執(zhí)行結(jié)果就完全正確了:80協(xié)調(diào)執(zhí)行緒要注意的是,對於物件a來說,只有在物件a的synchronized方法或是以a為共享資源的synchronized(a)區(qū)塊中才能呼叫a的wait()方法。一旦執(zhí)行緒進(jìn)入等待狀態(tài)時,就會暫時釋放其synchronized狀態(tài),就好像這個執(zhí)行緒已經(jīng)離開synchronized方法或是區(qū)塊一樣,讓其他的執(zhí)行緒可以呼叫同一方法或是進(jìn)入同一區(qū)塊。81協(xié)調(diào)執(zhí)行緒要注意的是,對於物件a來說,只有在物件a協(xié)調(diào)執(zhí)行緒等到其他執(zhí)行緒呼叫notify()喚醒等待的執(zhí)行緒時,被喚醒的執(zhí)行緒必須等到可以重新進(jìn)入synchronized狀態(tài)時才能繼續(xù)執(zhí)行。舉例來說,如果您把reportCount()的synchronized字符拿掉:82協(xié)調(diào)執(zhí)行緒等到其他執(zhí)行緒呼叫notify()喚醒等待的執(zhí)協(xié)調(diào)執(zhí)行緒83協(xié)調(diào)執(zhí)行緒83協(xié)調(diào)執(zhí)行緒84協(xié)調(diào)執(zhí)行緒84協(xié)調(diào)執(zhí)行緒執(zhí)行時就會發(fā)生錯誤:85協(xié)調(diào)執(zhí)行緒執(zhí)行時就會發(fā)生錯誤:85避免錯誤的synchronized寫法上一節(jié)曾經(jīng)提過使用synchronized來同步多個執(zhí)行緒,不過要特別注意的是,使用synchronized標(biāo)示方法時,其實就等於是以物件自己為共享資源標(biāo)示synchronized區(qū)塊,也就是說,以下的方法:86避免錯誤的synchronized寫法上一節(jié)曾經(jīng)提過使用避免錯誤的synchronized寫法其實就等於:87避免錯誤的synchronized寫法其實就等於:87避免錯誤的synchronized寫法因此,如果同一類別中有多個synchronized方法時,若是有執(zhí)行緒已進(jìn)入此類別物件的某個synchronized方法,就會造成其他執(zhí)行緒無法再呼叫同一物件的任一個synchronized方法。請看以下範(fàn)例:88避免錯誤的synchronized寫法因此,如果同一類避免錯誤的synchronized寫法89避免錯誤的synchronized寫法89避免錯誤的synchronized寫法90避免錯誤的synchronized寫法90避免錯誤的synchronized寫法91避免錯誤的synchronized寫法91避免錯誤的synchronized寫法在這個程式中,Lock類別有2個synchronized方法,分別叫做a()與b()。其中方法a()一執(zhí)行後就會進(jìn)入一個while迴圈等待方法b()被執(zhí)行;而方法b()則會設(shè)定bExecuted的值,讓方法a()的while迴圈可以結(jié)束。92避免錯誤的synchronized寫法在這個程式中,L避免錯誤的synchronized寫法ThreadA與ThreadB這兩個Thread子類別的run()方法則是分別呼叫Lock.obj這個物件的a()與b()方法。在main()方法中,就分別產(chǎn)生了ThreadA與ThreadB的物件ta與tb,然後分別啟動執(zhí)行緒。理論上程式執(zhí)行後t(yī)a執(zhí)行緒會呼叫Lock.obj.a()而等待,然後t(yī)b執(zhí)行緒會呼叫Lock.obj.b()解除a()的迴圈,最後程式結(jié)束。不過實際的執(zhí)行結(jié)果卻是這樣:93避免錯誤的synchronized寫法ThreadA與避免錯誤的synchronized寫法ta執(zhí)行緒一進(jìn)入迴圈後就跑不出來了,這表示tb執(zhí)行緒根本就沒有進(jìn)入方法b()。會造成這樣的結(jié)果,就是因為方法a()與b()是同一個物件的synchronized方法,當(dāng)ta執(zhí)行緒進(jìn)入方法a()後,就導(dǎo)致tb執(zhí)行緒無法呼叫方法b()。94避免錯誤的synchronized寫法ta執(zhí)行緒一進(jìn)入避免錯誤的synchronized寫法最後,變成ta執(zhí)行緒要等tb執(zhí)行緒來解除它的迴圈,可是tb執(zhí)行緒也在等ta執(zhí)行緒離開方法a(),彼此互相等待,所以程式就停在方法a()中無法繼續(xù)了。像這樣多個執(zhí)行緒間相互等待的狀況,就稱為死結(jié)(DeadLock)。如果將方法b()的synchronized字符拿掉,程式就可以順利進(jìn)行了:95避免錯誤的synchronized寫法最後,變成ta避免錯誤的synchronized寫法96避免錯誤的synchronized寫法96避免錯誤的synchronized寫法97避免錯誤的synchronized寫法97避免錯誤的synchronized寫法如果這兩個方法都必須是synchronized,那麼就必須改用wait()來進(jìn)行等待。因為在上一小節(jié)提過,當(dāng)執(zhí)行緒呼叫某物件的wait()而進(jìn)入等待狀態(tài)時,會先釋放對於該物件的synchronized狀態(tài),讓其它的執(zhí)行緒可以進(jìn)入以同一物件為共享資源的synchronized區(qū)塊:98避免錯誤的synchronized寫法如果這兩個方法都必避免錯誤的synchronized寫法99避免錯誤的synchronized寫法99避免錯誤的synchronized寫法要特別留意加上呼叫notify()的動作來解除等待狀態(tài),這是撰寫多執(zhí)行緒程式時很容易疏忽的地方。100避免錯誤的synchronized寫法10015-4綜合演練用來通知計時結(jié)束的介面實作計時器類別測試Timer類別10115-4綜合演練用來通知計時結(jié)束的介面101用來通知計時結(jié)束的介面在第12章曾經(jīng)提過,介面有一個用法就是提供給物件之間作為相互溝通的橋樑,我們首先要實作的就是這樣的介面,它可以讓計時器在計時結(jié)束時呼叫啟動該計時器的物件所提供的方法。102用來通知計時結(jié)束的介面在第12章曾經(jīng)提過,介面有一個用用來通知計時結(jié)束的介面要啟動計時器必須準(zhǔn)備一個實作有TimeUp介面的物件,並在啟動計時器時傳遞給計時器。當(dāng)計時器計時結(jié)束時,就會呼叫此TimeUp物件的notifyTimeUp()方法,通知該物件計時已經(jīng)結(jié)束。103用來通知計時結(jié)束的介面要啟動計時器必須準(zhǔn)備一個實作有Tim實作計時器類別有了TimeUp介面後,就可以實作計時器的類別了。程式如下:104實作計時器類別有了TimeUp介面後,就可以實作計時器實作計時器類別105實作計時器類別105實作計時器類別在Timer類別中,interval是用來記錄計時的長度,單位和Thread.sleep()方法一樣是1/1000秒。而listener就是記錄當(dāng)計時結(jié)束時要呼叫的notifyTimeUp()方法所屬的物件。static方法setTimer()就是實際啟動計時器的方法,它需要2個參數(shù),分別是計時的長度以及用來通知計時結(jié)束的TimeUp物件。此方法會建立一個Timer物件,然後呼叫start()方法啟動執(zhí)行緒開始計時。106實作計時器類別在Timer類別中,interval是實作計時器類別要注意的是,由於Timer類別一般的用法是呼叫其static方法setTimer(),因此其建構(gòu)方法是private存取控制。也就是說,除了呼叫setTimer()以外,您並不能自行建立Timer物件。這也是private建構(gòu)方法的一種用法。107實作計時器類別要注意的是,由於Timer類別一般的用法實作計時器類別在run()方法中,只是很單純的呼叫Thread.sleep()方法等待指定的時間,並且在等待完畢後呼叫l(wèi)istener物件的notifyTimeUp()方法,通知計時結(jié)束。到這裡,就將計時類別實作好了,接下來就可以實際運用。108實作計時器類別在run()方法中,只是很單純的呼叫T測試Timer類別這裡我們寫了一個簡單的測試程式,它會啟動一個5秒鐘的計時器,並顯示簡單的訊息。109測試Timer類別這裡我們寫了一個簡單的測試程式,它會測試Timer類別110測試Timer類別110測試Timer類別111測試Timer類別111測試Timer類別在TestTimer類別中,isTimeUp是用來標(biāo)示計時是否已經(jīng)結(jié)束的變數(shù),在main()中的主流程就依據(jù)這個變數(shù)的值來判斷是否要結(jié)束迴圈。main()方法中首先呼叫Timer.setTimer()啟動計時器,並傳入5000表示要計時5秒,另外傳入一個新產(chǎn)生的TestTimer物件,以作為計時結(jié)束時通知之用。112測試Timer類別在TestTimer類別中,is測試Timer類別程式接著先顯示目前時間,然後進(jìn)入一個while迴圈,利用Thread.sleep()等1秒,顯示簡單的“.”表示正在等待計時結(jié)束中。迴圈會一直進(jìn)行到isTimeUp被設(shè)定為true為止,最後再顯示目前的時間,然後結(jié)束程式。113測試Timer類別程式接著先顯示目前時間,然後進(jìn)入一個測試Timer類別由於TestTimer本身就實作了TimeUp介面,因此必須實作notifyTimeUp()方法。在這個方法中,只是很簡單的顯示時間到的訊息,並設(shè)定isTimeUp為true,讓main()中的迴圈可以結(jié)束。您也可以將Timer類別以及TimeUp介面放入套件中,以方便不同的程式共用這個工具類別。114測試Timer類別由於TestTimer本身就實作了第15章多執(zhí)行緒(Multithreading)115第15章多執(zhí)行緒(Multithreading)1本章提要15-1甚麼是執(zhí)行緒?15-2執(zhí)行緒的同步(Synchronization)15-3執(zhí)行緒間的協(xié)調(diào)15-4綜合演練116本章提要15-1甚麼是執(zhí)行緒?2前言到目前為止,我們所撰寫的程式流程都是從頭執(zhí)行到尾,同一時間只會進(jìn)行一件事??墒窃趯嶋H撰寫程式的時候,常常會遇到同一時間希望能夠進(jìn)行多件事情的狀況。舉例來說,在前幾章曾經(jīng)撰寫過一個碼錶程式,細(xì)心思考的讀者可能已經(jīng)發(fā)現(xiàn)到,這個碼錶程式其實一點用處都沒有。由於同一時間只能做一件事,因此碼錶程式除了倒數(shù)計時以外,甚麼事也不能做。117前言到目前為止,我們所撰寫的程式流程都是從頭執(zhí)行到尾,同前言我們所需要的是倒數(shù)的同時,另一邊又可以進(jìn)行其它工作的碼錶。這在現(xiàn)實生活中也很常見,像是最簡單的泡麵來說,將麵擺好,加入沸水後,可以準(zhǔn)備個倒數(shù)的鬧鐘,定好三分鐘後響。這樣一來,就可以看看書,等到鬧鐘響了,就可以享用熱騰騰的麵了。這泡麵、倒數(shù)、看書就是三件同時進(jìn)行的工作,如果同一時間只能進(jìn)行一件事,那可就麻煩了。118前言我們所需要的是倒數(shù)的同時,另一邊又可以進(jìn)行其它工作的碼前言Java就提供這種同時進(jìn)行多項工作的能力,稱為多執(zhí)行緒(Multithreading)。由於具有此項能力,使得撰寫Java程式時增加了許多的彈性,也使得程式撰寫起來更加直覺。119前言Java就提供這種同時進(jìn)行多項工作的能力,稱為多執(zhí)行15-1甚麼是執(zhí)行緒?要知道甚麼是執(zhí)行緒,其實並不難。想像一下一家製造汽車的工廠,為了達(dá)到最高的效率,工廠都會以生產(chǎn)線的方式,將汽車相互獨立的元件分開且同時製造。由於製造車身和製造引擎並不需要互相等待,因此可以有一條生產(chǎn)線製造車身、一條生產(chǎn)線製造引擎。這樣一來,車身和引擎就可能同時完成,馬上就可以進(jìn)行組裝。否則的話,如果車身要等引擎製造完成才能動工,那麼整臺車製造完成的時間就會拖長了。12015-1甚麼是執(zhí)行緒?要知道甚麼是執(zhí)行緒,其實並不難。想甚麼是執(zhí)行緒?如果將程式對比為製造汽車的工廠,那麼執(zhí)行緒就是工廠中的每一條生產(chǎn)線,可以和其他的執(zhí)行緒同時進(jìn)行手上的工作。也就是說,每一個執(zhí)行緒有它自己的流程,當(dāng)程式執(zhí)行時,每一個執(zhí)行緒便依據(jù)自己的流程進(jìn)行,同時處理各自的工作。121甚麼是執(zhí)行緒?如果將程式對比為製造汽車的工廠,那麼執(zhí)行緒就甚麼是執(zhí)行緒?122甚麼是執(zhí)行緒?8使用Thread類別建立執(zhí)行緒接下來我們就實際建立一個多執(zhí)行緒的程式,讓您可以觀察程式的執(zhí)行結(jié)果,以確實瞭解執(zhí)行緒的含意。在Java中,每一個執(zhí)行緒都是以一個Thread物件來表示,要建立新的執(zhí)行緒,最簡單的方法就是從Thread類別(屬於java.lang套件)衍生新的類別,並且重新定義Thread()類別中的run()方法,進(jìn)行這個新執(zhí)行緒所要負(fù)責(zé)的工作。例如:123使用Thread類別建立執(zhí)行緒接下來我們就實際建立一個多使用Thread類別建立執(zhí)行緒124使用Thread類別建立執(zhí)行緒10使用Thread類別建立執(zhí)行緒125使用Thread類別建立執(zhí)行緒11使用Thread類別建立執(zhí)行緒126使用Thread類別建立執(zhí)行緒12使用Thread類別建立執(zhí)行緒在第3行中,定義了一個Thread的子類別TimerThread,並且重新定義了run()方法,這個方法的內(nèi)容只是不斷的取得目前的時間,然後顯示在螢?zāi)簧?。這裡有兩件事需要注意:127使用Thread類別建立執(zhí)行緒在第3行中,定義了一使用Thread類別建立執(zhí)行緒取得時間的方法是產(chǎn)生一個java.util套件中的Date物件,這個物件的建構(gòu)方法會取得目前的時間,記錄下來。Date類別重新定義了toString()方法,可以將其記錄的日期時間以特定格式轉(zhuǎn)成字串。相關(guān)的說明請參考JDK文件。第6行的for迴圈是故意用來減緩程式顯示訊息的速度,避免不斷迅速地執(zhí)行第8行在螢?zāi)簧巷@示訊息,而無法閱讀結(jié)果。128使用Thread類別建立執(zhí)行緒取得時間的方法是產(chǎn)生一個使用Thread類別建立執(zhí)行緒在main()方法中,就建立了一個TimerThread物件,然後呼叫其start()方法。start()是繼承自Thread的方法,執(zhí)行後,就會建立一個新的執(zhí)行緒,然後在這個新的執(zhí)行緒中呼叫run()方法。從此開始,run()方法的執(zhí)行就和原本程式的流程分開,同時執(zhí)行。也就是說,新的執(zhí)行緒就從第5行開始執(zhí)行,而同時原本的程式流程則會從start()中返回,由第18行接續(xù)執(zhí)行。129使用Thread類別建立執(zhí)行緒在main()方法中使用Thread類別建立執(zhí)行緒main()方法中接下來的內(nèi)容就和TimerThread類別的run()方法相似,只是顯示的訊息開頭不同而已。由於main()方法與run()方法中各是兩個無窮迴圈,所以兩個執(zhí)行緒就不斷的顯示目前的日期時間。如果要結(jié)束程式,必須按下[Ctrl]+[C]鍵強(qiáng)迫終結(jié)。130使用Thread類別建立執(zhí)行緒main()方法中接下使用Thread類別建立執(zhí)行緒您可以從執(zhí)行結(jié)果中看出來,新執(zhí)行緒與原本的流程是交錯執(zhí)行的,剛開始新執(zhí)行緒先顯示訊息,然後舊流程插入,如此反覆執(zhí)行。如果再重新執(zhí)行程式,結(jié)果並不會完全相同,但仍然是新執(zhí)行緒與原始流程交錯執(zhí)行,而這也是多執(zhí)行緒最重要的一點。131使用Thread類別建立執(zhí)行緒您可以從執(zhí)行結(jié)果中看出來,使用Runnable介面建立執(zhí)行緒由於Java並不提供多重繼承,如果類別需要繼承其他類別,就沒有辦法再繼承Thread類別來建立執(zhí)行緒。對於這種狀況,Java提供有Ruunable介面,讓任何類別都可以用來建立執(zhí)行緒。請看以下的範(fàn)例:132使用Runnable介面建立執(zhí)行緒由於Java並不提使用Runnable介面建立執(zhí)行緒133使用Runnable介面建立執(zhí)行緒19使用Runnable介面建立執(zhí)行緒134使用Runnable介面建立執(zhí)行緒20使用Runnable介面建立執(zhí)行緒要透過Runnable介面建立執(zhí)行緒,第一步就是定義一個實作Runnable介面的類別,並重新定義run()方法。接著再使用需要單一參數(shù)的建構(gòu)方法來產(chǎn)生Thread物件,並且將實作有Runnable介面的物件傳給建構(gòu)方法。產(chǎn)生Thread物件之後,只要呼叫其start()方法就可以啟動新的執(zhí)行緒。135使用Runnable介面建立執(zhí)行緒要透過Runnabl執(zhí)行緒的各種狀態(tài)執(zhí)行緒除了能不斷的執(zhí)行以外,還可以依據(jù)需求切換到不同的狀態(tài)。舉例來說,在上一小節(jié)的範(fàn)例中,使用了一個並不進(jìn)行任何實質(zhì)動作的迴圈來延遲下一次顯示訊息的時間,相同的工作可以改成讓執(zhí)行緒進(jìn)入睡眠狀態(tài)一段時間,然後在時間到後繼續(xù)執(zhí)行:136執(zhí)行緒的各種狀態(tài)執(zhí)行緒除了能不斷的執(zhí)行以外,還可以依據(jù)需求執(zhí)行緒的各種狀態(tài)137執(zhí)行緒的各種狀態(tài)23執(zhí)行緒的各種狀態(tài)138執(zhí)行緒的各種狀態(tài)24執(zhí)行緒的各種狀態(tài)其中第7行就是呼叫Thread類別所定義的static方法

sleep(),讓新的執(zhí)行緒進(jìn)入睡眠狀態(tài)。sleep()方法的參數(shù)表示睡眠的時間,以毫秒(即1/1000秒)為單位,因此傳入1000就等於是1秒。要注意的是,呼叫sleep()方法可能會引發(fā)java.lang.InterruptedException例外,所以必須使用try...catch敘述攔截例外。139執(zhí)行緒的各種狀態(tài)其中第7行就是呼叫Thread類別所執(zhí)行緒的各種狀態(tài)相同的道理,原始的流程也一樣可以透過進(jìn)入睡眠狀態(tài)的方式,暫停執(zhí)行一段時間。sleep()方法會讓目前在執(zhí)行中的執(zhí)行緒進(jìn)入睡眠狀態(tài),因此第23行一樣呼叫sleep()睡眠1秒鐘。當(dāng)執(zhí)行緒進(jìn)入睡眠狀態(tài)時,其他的執(zhí)行緒仍然會繼續(xù)執(zhí)行,不會受到影響。140執(zhí)行緒的各種狀態(tài)相同的道理,原始的流程也一樣可以透過進(jìn)入睡除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況預(yù)備狀態(tài)(Ready):這個狀態(tài)表示執(zhí)行緒將排隊等待執(zhí)行,當(dāng)建立執(zhí)行緒物件並執(zhí)行start()方法後,就會進(jìn)入這個狀態(tài)。相同的道理,當(dāng)執(zhí)行緒結(jié)束睡眠狀態(tài)後,也會進(jìn)入此狀態(tài)等待執(zhí)行。執(zhí)行狀態(tài)(Running):這個狀態(tài)表示此執(zhí)行緒正在執(zhí)行,您可以呼叫Thread類別所定義的static方法currentThread(),取得代表目前流程的執(zhí)行緒的Thread物件。141除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況預(yù)備狀態(tài)除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況凍結(jié)狀態(tài)(Blocked):當(dāng)執(zhí)行緒執(zhí)行特定的處理,像是從磁碟讀取資料時,就會進(jìn)入凍結(jié)狀態(tài)。等到處理完畢,就會結(jié)束凍結(jié)狀態(tài),進(jìn)入預(yù)備狀態(tài)。另外,在下一節(jié)中也會介紹執(zhí)行緒的同步,因為sychronized區(qū)塊或是sychronized方法而等待其他執(zhí)行緒時,也會進(jìn)入凍結(jié)狀態(tài),詳細(xì)內(nèi)容請看15-2節(jié)。142除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況凍結(jié)狀態(tài)除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況等待狀態(tài)(Waiting):當(dāng)執(zhí)行緒呼叫Object類別所定義的wait()方法自願等待時,就會進(jìn)入等待狀態(tài),一直到其他執(zhí)行緒呼叫notify()或是notifyAll()方法解除其等待狀態(tài),才會再進(jìn)入預(yù)備狀態(tài)。有關(guān)wait()與notify()、notifyAll()方法等,請看15-2節(jié)。143除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況等待狀態(tài)除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況144除了睡眠狀態(tài)以外,執(zhí)行緒還可能會進(jìn)入以下幾種狀況3015-2執(zhí)行緒的同步(Synchronization)由於多個執(zhí)行緒是輪流執(zhí)行的,因此就可能發(fā)生某個執(zhí)行緒正在進(jìn)行一項工作的中途,因為分配的時間結(jié)束而暫停執(zhí)行,由另一個執(zhí)行緒執(zhí)行,等下次輪到它執(zhí)行時,才接續(xù)進(jìn)行之前的工作。14515-2執(zhí)行緒的同步(Synchronization)由執(zhí)行緒的同步(Synchronization)在這樣的情況下,如果多個執(zhí)行緒都會使用到同一個資源。比如說都會先取出某個變數(shù)值,進(jìn)行特定的運算之後修改該變數(shù),那麼就可能會造成執(zhí)行緒A先取出變數(shù)值,計算後還未修改變數(shù)值時輪到執(zhí)行緒B執(zhí)行,執(zhí)行緒B取得未修改的值,計算後修改變數(shù)值,等到再輪回執(zhí)行緒A時,便將之前計算的值放回變數(shù),剛剛執(zhí)行緒B的計算結(jié)果便不見了,使得計算結(jié)果不正確。146執(zhí)行緒的同步(Synchronization)在這樣的情況多執(zhí)行緒存取共用資源的問題請看看以下的範(fàn)例,就可以瞭解上述的狀況。假設(shè)總統(tǒng)大選時某候選人派出多個人進(jìn)駐各投票所蒐集得票數(shù),每間隔一段時間每個投票所會傳回該段時間新增的得票數(shù),並加總得出總得票數(shù)。如果使用個別的執(zhí)行緒來蒐集各投票所的得票,程式大概如下:147多執(zhí)行緒存取共用資源的問題請看看以下的範(fàn)例,就可以瞭解上述多執(zhí)行緒存取共用資源的問題148多執(zhí)行緒存取共用資源的問題34多執(zhí)行緒存取共用資源的問題149多執(zhí)行緒存取共用資源的問題35多執(zhí)行緒存取共用資源的問題150多執(zhí)行緒存取共用資源的問題36多執(zhí)行緒存取共用資源的問題151多執(zhí)行緒存取共用資源的問題37多執(zhí)行緒存取共用資源的問題152多執(zhí)行緒存取共用資源的問題38多執(zhí)行緒存取共用資源的問題PollingStation類別就代表了個別的投票所,其中name成員紀(jì)錄此投票所的名稱,reportTimes指的是投票所應(yīng)該回報新增得票數(shù)的次數(shù);而total則記錄此投票所目前的總票數(shù),v成員則記錄了要統(tǒng)計總票數(shù)的Vote物件。153多執(zhí)行緒存取共用資源的問題PollingStation類別多執(zhí)行緒存取共用資源的問題run()方法是執(zhí)行緒的主體,使用了一個迴圈回報新增得票數(shù)。我們使用了java.lang.Math類別所提供的static方法random()來產(chǎn)生一個介於0到1之間的double數(shù)值,將之乘上500後可以得到一個介於0~500的新增得票數(shù)。接著,就呼叫Vote類別的reportCount()方法回報新增的得票數(shù)。最後,再將新增得票數(shù)加入此投票所的總投票數(shù)。154多執(zhí)行緒存取共用資源的問題run()方法是執(zhí)行緒的主體,多執(zhí)行緒存取共用資源的問題在Vote類別中,total成員記錄總得票數(shù),而numOfStations則是投票所的數(shù)量,stations則是記錄所有PollingStation物件的陣列。reportCount()方法是提供給PollingStation物件回報新增投票數(shù)用,它會先顯示此投票所名稱以及新增的得票數(shù),然後再顯示總得票數(shù)。155多執(zhí)行緒存取共用資源的問題在Vote類別中,total多執(zhí)行緒存取共用資源的問題在startReport()方法中,一開始是一一產(chǎn)生代表投票所的物件,然後再啟動各個執(zhí)行緒。接著,就等待各個投票所開票完畢,等待的方法是呼叫Thread類別的join()方法,這個方法會等到對應(yīng)此Thread物件的執(zhí)行緒執(zhí)行完畢後才返回。156多執(zhí)行緒存取共用資源的問題在startReport()方多執(zhí)行緒存取共用資源的問題因此47~51行的迴圈結(jié)束時,就表示各開票所的執(zhí)行緒都已經(jīng)執(zhí)行完畢。join()方法也和sleep()方法一樣可能會引發(fā)InterruptedExeption例外,所以必須套用try...catch敘述。最後,將各開票所的總票數(shù)及總得票數(shù)顯示出來。157多執(zhí)行緒存取共用資源的問題因此47~51行多執(zhí)行緒存取共用資源的問題main()方法的內(nèi)容很簡單,只是產(chǎn)生1個Vote物件,然後呼叫其startReport()方法而已。158多執(zhí)行緒存取共用資源的問題main()方法的內(nèi)容很簡單,多執(zhí)行緒存取共用資源的問題159多執(zhí)行緒存取共用資源的問題45多執(zhí)行緒存取共用資源的問題從執(zhí)行結(jié)果的最後面就可以看到,明明兩個開票所總得票數(shù)分別是1529以及1034,為什麼總得票數(shù)卻是2072?原因很簡單,就出在reportCount()方法中。由於多個執(zhí)行緒是輪流執(zhí)行,因此當(dāng)某個執(zhí)行緒要將計算好的總票數(shù)放回total變數(shù)前,可能會中斷執(zhí)行。此時換其他的執(zhí)行緒執(zhí)行,也得到新增的得票數(shù),並計算新的總票數(shù)放入total中。160多執(zhí)行緒存取共用資源的問題從執(zhí)行結(jié)果的最後面就可以看到,明多執(zhí)行緒存取共用資源的問題而當(dāng)再輪回原本的執(zhí)行緒時,便將之前計算的總票數(shù)放入total變數(shù)中,導(dǎo)致剛剛由別的執(zhí)行緒所計算的總票數(shù)被蓋掉,計算出錯誤的結(jié)果。以下列出整個執(zhí)行的順序,就可以看到問題出在哪裡了:1號開票所開出423、337、278張票,所以總票數(shù)1038。161多執(zhí)行緒存取共用資源的問題而當(dāng)再輪回原本的執(zhí)行緒時,便將之多執(zhí)行緒存取共用資源的問題1號開票所再開出406張票,計算出總票數(shù)1444後,還未存入total變數(shù)中,便換成2號開票所執(zhí)行。2號開票所便以目前total的值1038為總票數(shù),繼續(xù)開出101、150、112張票,所以總數(shù)變成1401。接著2號開票所開出182張票,計算出總票數(shù)1583後,未放回total變數(shù)便換成1號開票所開票。162多執(zhí)行緒存取共用資源的問題1號開票所再開出406張票,多執(zhí)行緒存取共用資源的問題此時1號開票所將第2步中計算出的1444存回total,蓋掉了第3步中的結(jié)果。然後再開出85張票,所以總數(shù)變成1529。又再換成2號開票所,把第4步中計算的結(jié)果1583存回total,蓋掉了第5步中的結(jié)果。最後,再開出489張票,結(jié)果就變成總票數(shù)2072了。163多執(zhí)行緒存取共用資源的問題此時1號開票所將第2步中計多執(zhí)行緒存取共用資源的問題您可以看到,由於在第6步中蓋掉了total的內(nèi)容,等於是漏算了第2步中的406張票與第5步中的85張票了,因此總票數(shù)2072與真正的總票數(shù)1529+1034=2563差了491張票。可想而知,如果真的計票軟體這樣設(shè)計的話,候選人可都要跳腳了。164多執(zhí)行緒存取共用資源的問題您可以看到,由於在第6步中蓋使用synchronized區(qū)塊要解決上述的問題,必須要有一種方式,可以確保計算總票數(shù)以及將總票數(shù)放回total變數(shù)的過程中不會被中斷,這樣才能避免將總票數(shù)放回total變數(shù)時不會蓋掉正確的結(jié)果。165使用synchronized區(qū)塊要解決上述的問題,必須使用synchronized標(biāo)示方法在Java中,就提供有synchronized字符,可以讓您標(biāo)示一個同一時間僅能有一個執(zhí)行緒執(zhí)行的方法。只要將原來的reportCount()方法加上synchronized字符,程式就不會出錯了:166使用synchronized標(biāo)示方法在Java中,使用synchronized標(biāo)示方法167使用synchronized標(biāo)示方法53使用synchronized標(biāo)示方法加上synchronized字符後,只要有執(zhí)行緒正在執(zhí)行此方法,其他的執(zhí)行緒要想執(zhí)行同一個方法時,就會被強(qiáng)制暫停,等到目前的執(zhí)行緒執(zhí)行完此方法後才能繼續(xù)執(zhí)行。如此一來,就可以避免在存回總票數(shù)之前被中斷的情況,保證total的值一定會正確了。以下就是正確的執(zhí)行結(jié)果:168使用synchronized標(biāo)示方法加上synchro使用synchronized標(biāo)示方法169使用synchronized標(biāo)示方法55使用synchronized標(biāo)示程式區(qū)塊有的時候,只有方法中的一段程式需要保證同一時間僅有單一執(zhí)行緒可以執(zhí)行,這時您也可以只標(biāo)示需要保證完整執(zhí)行的程式區(qū)塊,而不必標(biāo)示整個方法。以我們的範(fàn)例來說,真正需要保證完整執(zhí)行的是取得total變數(shù)值到加總完存回total變數(shù)的這一段,因此程式可以改寫為這樣:170使用synchronized標(biāo)示程式區(qū)塊有的時候,只有使用synchronized標(biāo)示程式區(qū)塊171使用synchronized標(biāo)示程式區(qū)塊57使用synchronized標(biāo)示程式區(qū)塊其中就使用了synchronized來標(biāo)示第30~35行的內(nèi)容。要注意的是,此種標(biāo)示方法必須在synchronized字符之後指出執(zhí)行緒間所共用而造成問題的資源,這必須是一個指向物件的參照值。以我們的範(fàn)例來說,由於是多個執(zhí)行緒都去修改total變數(shù)而造成問題,但total變數(shù)並非是參照值,因此我們就以包含total變數(shù)的Vote物件本身作為共享的資源,來使用synchronized敘述。172使用synchronized標(biāo)示程式區(qū)塊其中就使用了s使用synchronized標(biāo)示程式區(qū)塊這樣一來,Java就會知道以下這個區(qū)塊內(nèi)的程式會因為多個執(zhí)行緒同時使用到指定的資源而造成問題,因而幫我們控制同一時間僅能有一個執(zhí)行緒執(zhí)行以下區(qū)塊。如果有其他執(zhí)行緒也想進(jìn)入此區(qū)塊,就會被強(qiáng)制暫停,等待目前執(zhí)行此區(qū)塊的執(zhí)行緒離開此區(qū)塊,才能繼續(xù)執(zhí)行。173使用synchronized標(biāo)示程式區(qū)塊這樣一來,Ja15-3執(zhí)行緒間的協(xié)調(diào)使用多執(zhí)行緒時,經(jīng)常會遇到的一種狀況,就是執(zhí)行緒A在等執(zhí)行緒B完成某項工作,而當(dāng)執(zhí)行緒B完成後,執(zhí)行緒B又要等執(zhí)行緒A消化執(zhí)行緒B剛剛完成的工作,如此反覆進(jìn)行。這時候,就需要一種機(jī)制,可以讓執(zhí)行緒之間互相協(xié)調(diào),彼此可以知道對方的進(jìn)展。17415-3執(zhí)行緒間的協(xié)調(diào)使用多執(zhí)行緒時,經(jīng)常會遇到的一種狀執(zhí)行緒間相互合作的問題如果上一節(jié)的選票統(tǒng)計是由派駐各開票所的人員打電話回來給候選人的選舉總部,而總部僅有一名助理負(fù)責(zé)接聽電話,當(dāng)助理接聽電話時,自然其他開票所的人員就沒辦法打進(jìn)來。助理接聽電話時,必須負(fù)責(zé)將新增得票數(shù)記錄下來,然後通知選舉總部負(fù)責(zé)人去加總票數(shù)。那麼程式就可能是這樣:175執(zhí)行緒間相互合作的問題如果上一節(jié)的選票統(tǒng)計是由派駐各開票所的執(zhí)行緒間相互合作的問題176執(zhí)行緒間相互合作的問題62執(zhí)行緒間相互合作的問題177執(zhí)行緒間相互合作的問題63執(zhí)行緒間相互合作的問題178執(zhí)行緒間相互合作的問題64執(zhí)行緒間相互合作的問題179執(zhí)行緒間相互合作的問題65執(zhí)行緒間相互合作的問題180執(zhí)行緒間相互合作的問題66執(zhí)行緒間相互合作的問題181執(zhí)行緒間相互合作的問題67執(zhí)行緒間相互合作的問題其中22~35行就是用來表示選舉總部助理的Assistant類別。這個類別只有2個方法,分別是用來讓PollingStation物件呼叫,以回報新增得票數(shù)的reportCount()方法,以及提供給主流程取得新增票數(shù)的getCount()方法。由於這2個方法都必須存取共用的資源count,所以都以synchronized來控制存取count變數(shù)的同步。182執(zhí)行緒間相互合作的問題其中22~35行就是用來表示選執(zhí)行緒間相互合作的問題reportCount()方法中是顯示並且記錄回報的票數(shù),而getCount()方法就很單純的傳回count。PollingStation類別的內(nèi)容和之前的範(fàn)例幾乎一樣,不同的只是原來記錄Vote物件的變數(shù)改成記錄Assistant物件,並且一併改成呼叫Assistant類別的reportCount()方法回報新增票數(shù)。183執(zhí)行緒間相互合作的問題reportCount()方法中是顯執(zhí)行緒間相互合作的問題在main()方法中,先產(chǎn)生代表助理的Assistant物件,然後一一產(chǎn)生各個開票所的PollingStation物件,啟動執(zhí)行緒。然後使用for迴圈依據(jù)開票所的數(shù)量以及個別開票所回報新增票數(shù)的次數(shù)呼叫Assistant的getCount()方法,取得新增票數(shù)加總。最後,顯示各開票所的總票數(shù)以及加總的總票數(shù)。184執(zhí)行緒間相互合作的問題在main()方法中,先產(chǎn)生代表執(zhí)行緒間相互合作的問題185執(zhí)行緒間相互合作的問題71執(zhí)行緒間相互合作的問題最後的結(jié)果明明兩個開票所分別有897及1045票,怎麼總票數(shù)會是1088票呢?仔細(xì)看執(zhí)行結(jié)果會發(fā)現(xiàn),主流程在開票所還沒有回報票數(shù)時,就已經(jīng)先呼叫g(shù)etCount()兩次。更糟的是,總部負(fù)責(zé)人還沒有將記錄下來的新增票數(shù)加總,就又把傳回的新增票數(shù)記錄到count變數(shù)中,蓋掉了之前的數(shù)值,以致於最後的1088票其實是2號開票所最後一次傳回的136票乘上8次的結(jié)果。186執(zhí)行緒間相互合作的問題最後的結(jié)果明明兩個開票所分別有897執(zhí)行緒間相互合作的問題這個程式的問題就出在各個執(zhí)行緒間並沒有協(xié)調(diào)好,助理應(yīng)該告訴總部負(fù)責(zé)人現(xiàn)在沒有資料,請等候;相同的道理,助理也應(yīng)該要知道負(fù)責(zé)人還沒加總好資料,先不要把資料記下來,蓋掉舊的資料。187執(zhí)行緒間相互合作的問題這個程式的問題就出在各個執(zhí)行緒間並沒有協(xié)調(diào)執(zhí)行緒為了解決上述的問題,很顯然的,必須修改Assistant類別,讓它扮演好助理的角色。在Object類別中,提供有一對方法:wait()與notify(),wait()可以讓目前的執(zhí)行緒進(jìn)入等待狀態(tài),直到有別的執(zhí)行緒呼叫同一個物件的notify()方法叫醒,才會繼續(xù)執(zhí)行。因此,我們就可以呼叫Assistant物件的這一對方法來協(xié)調(diào)回報新增票數(shù)與加總票數(shù)的工作:188協(xié)調(diào)執(zhí)行緒為了解決上述的問題,很顯然的,必須修改Ass協(xié)調(diào)執(zhí)行緒189協(xié)調(diào)執(zhí)行緒75協(xié)調(diào)執(zhí)行緒190協(xié)調(diào)執(zhí)行緒76協(xié)調(diào)執(zhí)行緒第24行新增了unprocessedData成員,用來表示是否有新增的票數(shù)還未加總。191協(xié)調(diào)執(zhí)行緒第24行新增了unprocessedData協(xié)調(diào)執(zhí)行緒第29行的while迴圈會在還有新增票數(shù)未加總時讓代表開票所的執(zhí)行緒等待,等待的方式就是呼叫Assistant物件的wait()方法,進(jìn)入等待狀態(tài)。一旦被喚醒繼續(xù)執(zhí)行,就會將新增票數(shù)記錄下來,將unprocessedData設(shè)為true,告訴助理可以加總票數(shù)了。然後呼叫notify(),讓等待加總票數(shù)的主流程可以繼續(xù)執(zhí)行。192協(xié)調(diào)執(zhí)行緒第29行的while迴圈會在還有新增票數(shù)未協(xié)調(diào)執(zhí)行緒第43的while迴圈會在沒有新增票數(shù)需要加總時,讓負(fù)責(zé)加總的主流程等待,等待的方式一樣是呼叫Assistant物件的wait()方法。一旦被喚醒繼續(xù)執(zhí)行時,就會將unprocessedData設(shè)為false,並呼叫Assistant物件的notify()方法,以便喚醒等待回報新增票數(shù)的執(zhí)行緒,讓開票所能夠繼續(xù)回報新增票數(shù)。193協(xié)調(diào)執(zhí)行緒第43的while迴圈會在沒有新增票數(shù)需要協(xié)調(diào)執(zhí)行緒如此一來,執(zhí)行結(jié)果就完全正確了:194協(xié)調(diào)執(zhí)行緒如此一來,執(zhí)行結(jié)果就完全正確了:80協(xié)調(diào)執(zhí)行緒要注意的是,對於物件a來說,只有在物件a的synchronized方法或是以a為共享資源的synchronized(a)區(qū)

溫馨提示

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

評論

0/150

提交評論