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

下載本文檔

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

文檔簡介

1、第 15 章多執(zhí)行緒 (Multithreading)1本章提要15-1 甚麼是執(zhí)行緒?15-2 執(zhí)行緒的同步 (Synchronization)15-3 執(zhí)行緒間的協(xié)調(diào)15-4 綜合演練2前言到目前為止, 我們所撰寫的程式流程都是從頭執(zhí)行到尾, 同一時間只會進行一件事??墒窃趯嶋H撰寫程式的時候, 常常會遇到同一時間希望能夠進行多件事情的狀況。舉例來說, 在前幾章曾經(jīng)撰寫過一個碼錶程式, 細心思考的讀者可能已經(jīng)發(fā)現(xiàn)到, 這個碼錶程式其實一點用處都沒有。由於同一時間只能做一件事, 因此碼錶程式除了倒數(shù)計時以外, 甚麼事也不能做。3前言我們所需要的是倒數(shù)的同時, 另一邊又可以進行其它工作的碼錶。這

2、在現(xiàn)實生活中也很常見, 像是最簡單的泡麵來說, 將麵擺好, 加入沸水後, 可以準備個倒數(shù)的鬧鐘, 定好三分鐘後響。這樣一來, 就可以看看書, 等到鬧鐘響了, 就可以享用熱騰騰的麵了。這泡麵、倒數(shù)、看書就是三件同時進行的工作, 如果同一時間只能進行一件事, 那可就麻煩了。4前言Java 就提供這種同時進行多項工作的能力, 稱為多執(zhí)行緒 (Multithreading)。由於具有此項能力, 使得撰寫 Java 程式時增加了許多的彈性, 也使得程式撰寫起來更加直覺。515-1 甚麼是執(zhí)行緒?要知道甚麼是執(zhí)行緒, 其實並不難。想像一下一家製造汽車的工廠, 為了達到最高的效率, 工廠都會以生產(chǎn)線的方式,

3、 將汽車相互獨立的元件分開且同時製造。由於製造車身和製造引擎並不需要互相等待, 因此可以有一條生產(chǎn)線製造車身、一條生產(chǎn)線製造引擎。這樣一來, 車身和引擎就可能同時完成, 馬上就可以進行組裝。否則的話, 如果車身要等引擎製造完成才能動工, 那麼整臺車製造完成的時間就會拖長了。6甚麼是執(zhí)行緒?如果將程式對比為製造汽車的工廠, 那麼執(zhí)行緒就是工廠中的每一條生產(chǎn)線, 可以和其他的執(zhí)行緒同時進行手上的工作。也就是說, 每一個執(zhí)行緒有它自己的流程, 當程式執(zhí)行時, 每一個執(zhí)行緒便依據(jù)自己的流程進行, 同時處理各自的工作。7甚麼是執(zhí)行緒?8使用 Thread 類別建立執(zhí)行緒接下來我們就實際建立一個多執(zhí)行緒的

4、程式, 讓您可以觀察程式的執(zhí)行結果, 以確實瞭解執(zhí)行緒的含意。在 Java 中, 每一個執(zhí)行緒都是以一個 Thread 物件來表示, 要建立新的執(zhí)行緒, 最簡單的方法就是從 Thread 類別 (屬於 java.lang 套件) 衍生新的類別, 並且重新定義 Thread( ) 類別中的 run() 方法, 進行這個新執(zhí)行緒所要負責的工作。例如:9使用 Thread 類別建立執(zhí)行緒10使用 Thread 類別建立執(zhí)行緒11使用 Thread 類別建立執(zhí)行緒12使用 Thread 類別建立執(zhí)行緒在第 3 行中, 定義了一個 Thread 的子類別 TimerThread, 並且重新定義了run(

5、 ) 方法, 這個方法的內(nèi)容只是不斷的取得目前的時間, 然後顯示在螢幕上。這裡有兩件事需要注意:13使用 Thread 類別建立執(zhí)行緒取得時間的方法是產(chǎn)生一個 java.util 套件中的 Date 物件, 這個物件的建構方法會取得目前的時間, 記錄下來。Date 類別重新定義了 toString( ) 方法, 可以將其記錄的日期時間以特定格式轉成字串。相關的說明請參考 JDK 文件。第 6 行的 for 迴圈是故意用來減緩程式顯示訊息的速度, 避免不斷迅速地執(zhí)行第 8 行在螢幕上顯示訊息, 而無法閱讀結果。14使用 Thread 類別建立執(zhí)行緒在 main( ) 方法中, 就建立了一個 Ti

6、merThread 物件, 然後呼叫其 start() 方法。start( ) 是繼承自 Thread 的方法, 執(zhí)行後, 就會建立一個新的執(zhí)行緒, 然後在這個新的執(zhí)行緒中呼叫 run( ) 方法。從此開始, run( ) 方法的執(zhí)行就和原本程式的流程分開, 同時執(zhí)行。也就是說, 新的執(zhí)行緒就從第 5 行開始執(zhí)行, 而同時原本的程式流程則會從 start( ) 中返回, 由第 18 行接續(xù)執(zhí)行。15使用 Thread 類別建立執(zhí)行緒main( ) 方法中接下來的內(nèi)容就和 TimerThread 類別的 run( ) 方法相似, 只是顯示的訊息開頭不同而已。由於 main( ) 方法與 run(

7、 ) 方法中各是兩個無窮迴圈, 所以兩個執(zhí)行緒就不斷的顯示目前的日期時間。如果要結束程式, 必須按下 Ctrl + C 鍵強迫終結。16使用 Thread 類別建立執(zhí)行緒您可以從執(zhí)行結果中看出來, 新執(zhí)行緒與原本的流程是交錯執(zhí)行的, 剛開始新執(zhí)行緒先顯示訊息, 然後舊流程插入, 如此反覆執(zhí)行。如果再重新執(zhí)行程式, 結果並不會完全相同, 但仍然是新執(zhí)行緒與原始流程交錯執(zhí)行, 而這也是多執(zhí)行緒最重要的一點。17使用 Runnable 介面建立執(zhí)行緒由於 Java 並不提供多重繼承, 如果類別需要繼承其他類別, 就沒有辦法再繼承 Thread 類別來建立執(zhí)行緒。對於這種狀況, Java 提供有 Ru

8、unable 介面, 讓任何類別都可以用來建立執(zhí)行緒。請看以下的範例:18使用 Runnable 介面建立執(zhí)行緒19使用 Runnable 介面建立執(zhí)行緒20使用 Runnable 介面建立執(zhí)行緒要透過 Runnable 介面建立執(zhí)行緒, 第一步就是定義一個實作 Runnable 介面的類別, 並重新定義 run( ) 方法。接著再使用需要單一參數(shù)的建構方法來產(chǎn)生 Thread 物件, 並且將實作有 Runnable 介面的物件傳給建構方法。產(chǎn)生Thread 物件之後, 只要呼叫其 start( ) 方法就可以啟動新的執(zhí)行緒。21執(zhí)行緒的各種狀態(tài)執(zhí)行緒除了能不斷的執(zhí)行以外, 還可以依據(jù)需求切換

9、到不同的狀態(tài)。舉例來說, 在上一小節(jié)的範例中, 使用了一個並不進行任何實質(zhì)動作的迴圈來延遲下一次顯示訊息的時間, 相同的工作可以改成讓執(zhí)行緒進入睡眠狀態(tài)一段時間, 然後在時間到後繼續(xù)執(zhí)行:22執(zhí)行緒的各種狀態(tài)23執(zhí)行緒的各種狀態(tài)24執(zhí)行緒的各種狀態(tài)其中第 7 行就是呼叫 Thread 類別所定義的 static 方法 sleep(), 讓新的執(zhí)行緒進入睡眠狀態(tài)。sleep( ) 方法的參數(shù)表示睡眠的時間, 以毫秒 (即 1 / 1000 秒) 為單位, 因此傳入 1000 就等於是 1 秒。要注意的是, 呼叫 sleep( ) 方法可能會引發(fā) java.lang.InterruptedExce

10、ption 例外, 所以必須使用 try.catch 敘述攔截例外。25執(zhí)行緒的各種狀態(tài)相同的道理, 原始的流程也一樣可以透過進入睡眠狀態(tài)的方式, 暫停執(zhí)行一段時間。sleep( ) 方法會讓目前在執(zhí)行中的執(zhí)行緒進入睡眠狀態(tài), 因此第 23 行一樣呼叫 sleep( ) 睡眠 1 秒鐘。當執(zhí)行緒進入睡眠狀態(tài)時, 其他的執(zhí)行緒仍然會繼續(xù)執(zhí)行, 不會受到影響。26除了睡眠狀態(tài)以外, 執(zhí)行緒還可能會進入以下幾種狀況預備狀態(tài) (Ready) :這個狀態(tài)表示執(zhí)行緒將排隊等待執(zhí)行, 當建立執(zhí)行緒物件並執(zhí)行 start( ) 方法後, 就會進入這個狀態(tài)。相同的道理, 當執(zhí)行緒結束睡眠狀態(tài)後, 也會進入此狀態(tài)

11、等待執(zhí)行。執(zhí)行狀態(tài) (Running) :這個狀態(tài)表示此執(zhí)行緒正在執(zhí)行, 您可以呼叫 Thread 類別所定義的 static 方法 currentThread() , 取得代表目前流程的執(zhí)行緒的 Thread 物件。27除了睡眠狀態(tài)以外, 執(zhí)行緒還可能會進入以下幾種狀況凍結狀態(tài) (Blocked) :當執(zhí)行緒執(zhí)行特定的處理, 像是從磁碟讀取資料時, 就會進入凍結狀態(tài)。等到處理完畢, 就會結束凍結狀態(tài), 進入預備狀態(tài)。另外, 在下一節(jié)中也會介紹執(zhí)行緒的同步, 因為 sychronized 區(qū)塊或是 sychronized 方法而等待其他執(zhí)行緒時, 也會進入凍結狀態(tài), 詳細內(nèi)容請看 15-2 節(jié)

12、。28除了睡眠狀態(tài)以外, 執(zhí)行緒還可能會進入以下幾種狀況等待狀態(tài) (Waiting):當執(zhí)行緒呼叫 Object 類別所定義的 wait() 方法自願等待時, 就會進入等待狀態(tài), 一直到其他執(zhí)行緒呼叫 notify() 或是 notifyAll() 方法解除其等待狀態(tài), 才會再進入預備狀態(tài)。有關 wait() 與 notify() 、notifyAll() 方法等, 請看 15-2 節(jié)。29除了睡眠狀態(tài)以外, 執(zhí)行緒還可能會進入以下幾種狀況3015-2 執(zhí)行緒的同步 (Synchronization)由於多個執(zhí)行緒是輪流執(zhí)行的, 因此就可能發(fā)生某個執(zhí)行緒正在進行一項工作的中途, 因為分配的時間

13、結束而暫停執(zhí)行, 由另一個執(zhí)行緒執(zhí)行, 等下次輪到它執(zhí)行時, 才接續(xù)進行之前的工作。31執(zhí)行緒的同步 (Synchronization)在這樣的情況下, 如果多個執(zhí)行緒都會使用到同一個資源。比如說都會先取出某個變數(shù)值, 進行特定的運算之後修改該變數(shù), 那麼就可能會造成執(zhí)行緒 A 先取出變數(shù)值, 計算後還未修改變數(shù)值時輪到執(zhí)行緒 B 執(zhí)行, 執(zhí)行緒 B 取得未修改的值, 計算後修改變數(shù)值, 等到再輪回執(zhí)行緒 A 時, 便將之前計算的值放回變數(shù), 剛剛執(zhí)行緒 B 的計算結果便不見了, 使得計算結果不正確。32多執(zhí)行緒存取共用資源的問題請看看以下的範例, 就可以瞭解上述的狀況。假設總統(tǒng)大選時某候選人

14、派出多個人進駐各投票所蒐集得票數(shù), 每間隔一段時間每個投票所會傳回該段時間新增的得票數(shù), 並加總得出總得票數(shù)。如果使用個別的執(zhí)行緒來蒐集各投票所的得票, 程式大概如下:33多執(zhí)行緒存取共用資源的問題34多執(zhí)行緒存取共用資源的問題35多執(zhí)行緒存取共用資源的問題36多執(zhí)行緒存取共用資源的問題37多執(zhí)行緒存取共用資源的問題38多執(zhí)行緒存取共用資源的問題PollingStation 類別就代表了個別的投票所, 其中 name 成員紀錄此投票所的名稱, reportTimes 指的是投票所應該回報新增得票數(shù)的次數(shù);而 total 則記錄此投票所目前的總票數(shù), v 成員則記錄了要統(tǒng)計總票數(shù)的 Vote 物

15、件。39多執(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í)行緒存取共用資源的問題在 Vote 類別中, total 成員記錄總得票數(shù), 而 numOfStations 則是投票所的數(shù)量, stations

16、則是記錄所有 PollingStation 物件的陣列。reportCount() 方法是提供給 PollingStation 物件回報新增投票數(shù)用, 它會先顯示此投票所名稱以及新增的得票數(shù), 然後再顯示總得票數(shù)。41多執(zhí)行緒存取共用資源的問題在 startReport() 方法中, 一開始是一一產(chǎn)生代表投票所的物件, 然後再啟動各個執(zhí)行緒。接著, 就等待各個投票所開票完畢, 等待的方法是呼叫 Thread 類別的 join() 方法, 這個方法會等到對應此 Thread 物件的執(zhí)行緒執(zhí)行完畢後才返回。42多執(zhí)行緒存取共用資源的問題 因此 47 51 行的迴圈結束時, 就表示各開票所的執(zhí)行緒都

17、已經(jīng)執(zhí)行完畢。join() 方法也和 sleep() 方法一樣可能會引發(fā) Interrupted Exeption 例外, 所以必須套用 try.catch 敘述。最後, 將各開票所的總票數(shù)及總得票數(shù)顯示出來。43多執(zhí)行緒存取共用資源的問題main() 方法的內(nèi)容很簡單, 只是產(chǎn)生 1 個 Vote 物件, 然後呼叫其 startReport() 方法而已。44多執(zhí)行緒存取共用資源的問題45多執(zhí)行緒存取共用資源的問題從執(zhí)行結果的最後面就可以看到, 明明兩個開票所總得票數(shù)分別是 1529 以及 1034, 為什麼總得票數(shù)卻是 2072 ?原因很簡單, 就出在 reportCount() 方法中。

18、由於多個執(zhí)行緒是輪流執(zhí)行, 因此當某個執(zhí)行緒要將計算好的總票數(shù)放回 total 變數(shù)前, 可能會中斷執(zhí)行。此時換其他的執(zhí)行緒執(zhí)行, 也得到新增的得票數(shù), 並計算新的總票數(shù)放入 total 中。46多執(zhí)行緒存取共用資源的問題而當再輪回原本的執(zhí)行緒時, 便將之前計算的總票數(shù)放入 total 變數(shù)中, 導致剛剛由別的執(zhí)行緒所計算的總票數(shù)被蓋掉, 計算出錯誤的結果。以下列出整個執(zhí)行的順序, 就可以看到問題出在哪裡了:1 號開票所開出 423、337、278 張票, 所以總票數(shù) 1038。47多執(zhí)行緒存取共用資源的問題1 號開票所再開出 406 張票, 計算出總票數(shù) 1444 後, 還未存入 total

19、 變數(shù)中, 便換成 2 號開票所執(zhí)行。2 號開票所便以目前 total 的值 1038 為總票數(shù), 繼續(xù)開出 101、150、112 張票, 所以總數(shù)變成 1401。接著 2 號開票所開出 182 張票, 計算出總票數(shù) 1583 後, 未放回 total 變數(shù)便換成 1 號開票所開票。48多執(zhí)行緒存取共用資源的問題此時 1 號開票所將第 2 步中計算出的 1444 存回 total, 蓋掉了第 3 步中的結果。然後再開出 85 張票, 所以總數(shù)變成 1529。又再換成 2 號開票所, 把第 4 步中計算的結果 1583 存回 total, 蓋掉了第 5 步中的結果。最後, 再開出 489 張票

20、, 結果就變成總票數(shù) 2072 了。49多執(zhí)行緒存取共用資源的問題您可以看到, 由於在第 6 步中蓋掉了 total 的內(nèi)容, 等於是漏算了第 2 步中的 406 張票與第 5 步中的 85 張票了, 因此總票數(shù) 2072 與真正的總票數(shù) 1529 +1034 = 2563 差了 491 張票??上攵? 如果真的計票軟體這樣設計的話, 候選人可都要跳腳了。50使用 synchronized 區(qū)塊要解決上述的問題, 必須要有一種方式, 可以確保計算總票數(shù)以及將總票數(shù)放回 total 變數(shù)的過程中不會被中斷, 這樣才能避免將總票數(shù)放回 total 變數(shù)時不會蓋掉正確的結果。51使用 synchr

21、onized 標示方法在 Java 中, 就提供有 synchronized 字符, 可以讓您標示一個同一時間僅能有一個執(zhí)行緒執(zhí)行的方法。只要將原來的 reportCount() 方法加上 synchronized 字符, 程式就不會出錯了:52使用 synchronized 標示方法53使用 synchronized 標示方法加上 synchronized 字符後, 只要有執(zhí)行緒正在執(zhí)行此方法, 其他的執(zhí)行緒要想執(zhí)行同一個方法時, 就會被強制暫停, 等到目前的執(zhí)行緒執(zhí)行完此方法後才能繼續(xù)執(zhí)行。如此一來, 就可以避免在存回總票數(shù)之前被中斷的情況, 保證 total 的值一定會正確了。以下就是正

22、確的執(zhí)行結果:54使用 synchronized 標示方法55使用 synchronized 標示程式區(qū)塊有的時候, 只有方法中的一段程式需要保證同一時間僅有單一執(zhí)行緒可以執(zhí)行, 這時您也可以只標示需要保證完整執(zhí)行的程式區(qū)塊, 而不必標示整個方法。以我們的範例來說, 真正需要保證完整執(zhí)行的是取得 total 變數(shù)值到加總完存回 total 變數(shù)的這一段, 因此程式可以改寫為這樣:56使用 synchronized 標示程式區(qū)塊57使用 synchronized 標示程式區(qū)塊其中就使用了 synchronized 來標示第 30 35 行的內(nèi)容。要注意的是, 此種標示方法必須在 synchron

23、ized 字符之後指出執(zhí)行緒間所共用而造成問題的資源, 這必須是一個指向物件的參照值。以我們的範例來說, 由於是多個執(zhí)行緒都去修改 total 變數(shù)而造成問題, 但 total 變數(shù)並非是參照值, 因此我們就以包含 total 變數(shù)的 Vote 物件本身作為共享的資源, 來使用 synchronized 敘述。58使用 synchronized 標示程式區(qū)塊這樣一來, Java 就會知道以下這個區(qū)塊內(nèi)的程式會因為多個執(zhí)行緒同時使用到指定的資源而造成問題, 因而幫我們控制同一時間僅能有一個執(zhí)行緒執(zhí)行以下區(qū)塊。如果有其他執(zhí)行緒也想進入此區(qū)塊, 就會被強制暫停, 等待目前執(zhí)行此區(qū)塊的執(zhí)行緒離開此區(qū)塊

24、, 才能繼續(xù)執(zhí)行。5915-3 執(zhí)行緒間的協(xié)調(diào)使用多執(zhí)行緒時, 經(jīng)常會遇到的一種狀況, 就是執(zhí)行緒 A 在等執(zhí)行緒 B 完成某項工作, 而當執(zhí)行緒 B 完成後, 執(zhí)行緒 B 又要等執(zhí)行緒 A 消化執(zhí)行緒 B 剛剛完成的工作, 如此反覆進行。這時候, 就需要一種機制, 可以讓執(zhí)行緒之間互相協(xié)調(diào), 彼此可以知道對方的進展。60執(zhí)行緒間相互合作的問題如果上一節(jié)的選票統(tǒng)計是由派駐各開票所的人員打電話回來給候選人的選舉總部, 而總部僅有一名助理負責接聽電話, 當助理接聽電話時, 自然其他開票所的人員就沒辦法打進來。助理接聽電話時, 必須負責將新增得票數(shù)記錄下來, 然後通知選舉總部負責人去加總票數(shù)。那麼程

25、式就可能是這樣:61執(zhí)行緒間相互合作的問題62執(zhí)行緒間相互合作的問題63執(zhí)行緒間相互合作的問題64執(zhí)行緒間相互合作的問題65執(zhí)行緒間相互合作的問題66執(zhí)行緒間相互合作的問題67執(zhí)行緒間相互合作的問題其中 22 35 行就是用來表示選舉總部助理的 Assistant 類別。這個類別只有 2 個方法, 分別是用來讓 PollingStation 物件呼叫, 以回報新增得票數(shù)的reportCount() 方法, 以及提供給主流程取得新增票數(shù)的 getCount() 方法。由於這 2 個方法都必須存取共用的資源 count, 所以都以 synchronized 來控制存取count 變數(shù)的同步。68執(zhí)

26、行緒間相互合作的問題reportCount() 方法中是顯示並且記錄回報的票數(shù), 而getCount() 方法就很單純的傳回 count。PollingStation 類別的內(nèi)容和之前的範例幾乎一樣, 不同的只是原來記錄 Vote 物件的變數(shù)改成記錄 Assistant 物件, 並且一併改成呼叫 Assistant 類別的 reportCount() 方法回報新增票數(shù)。69執(zhí)行緒間相互合作的問題在 main() 方法中, 先產(chǎn)生代表助理的 Assistant 物件, 然後一一產(chǎn)生各個開票所的 PollingStation 物件, 啟動執(zhí)行緒。然後使用 for 迴圈依據(jù)開票所的數(shù)量以及個別開票所

27、回報新增票數(shù)的次數(shù)呼叫 Assistant 的 getCount() 方法, 取得新增票數(shù)加總。最後, 顯示各開票所的總票數(shù)以及加總的總票數(shù)。70執(zhí)行緒間相互合作的問題71執(zhí)行緒間相互合作的問題最後的結果明明兩個開票所分別有 897 及 1045 票, 怎麼總票數(shù)會是 1088 票呢?仔細看執(zhí)行結果會發(fā)現(xiàn), 主流程在開票所還沒有回報票數(shù)時, 就已經(jīng)先呼叫 getCount() 兩次。更糟的是, 總部負責人還沒有將記錄下來的新增票數(shù)加總, 就又把傳回的新增票數(shù)記錄到 count 變數(shù)中, 蓋掉了之前的數(shù)值, 以致於最後的 1088 票其實是 2 號開票所最後一次傳回的 136 票乘上 8 次的結

28、果。72執(zhí)行緒間相互合作的問題這個程式的問題就出在各個執(zhí)行緒間並沒有協(xié)調(diào)好, 助理應該告訴總部負責人現(xiàn)在沒有資料, 請等候;相同的道理, 助理也應該要知道負責人還沒加總好資料, 先不要把資料記下來, 蓋掉舊的資料。73協(xié)調(diào)執(zhí)行緒為了解決上述的問題, 很顯然的, 必須修改 Assistant 類別, 讓它扮演好助理的角色。在 Object 類別中, 提供有一對方法:wait() 與 notify() , wait() 可以讓目前的執(zhí)行緒進入等待狀態(tài), 直到有別的執(zhí)行緒呼叫同一個物件的 notify() 方法叫醒, 才會繼續(xù)執(zhí)行。因此, 我們就可以呼叫 Assistant 物件的這一對方法來協(xié)調(diào)回

29、報新增票數(shù)與加總票數(shù)的工作:74協(xié)調(diào)執(zhí)行緒75協(xié)調(diào)執(zhí)行緒76協(xié)調(diào)執(zhí)行緒第 24 行新增了 unprocessedData 成員, 用來表示是否有新增的票數(shù)還未加總。77協(xié)調(diào)執(zhí)行緒第 29 行的 while 迴圈會在還有新增票數(shù)未加總時讓代表開票所的執(zhí)行緒等待, 等待的方式就是呼叫 Assistant 物件的 wait() 方法, 進入等待狀態(tài)。 一旦被喚醒繼續(xù)執(zhí)行, 就會將新增票數(shù)記錄下來, 將 unprocessedData 設為 true, 告訴助理可以加總票數(shù)了。然後呼叫 notify() , 讓等待加總票數(shù)的主流程可以繼續(xù)執(zhí)行。78協(xié)調(diào)執(zhí)行緒第 43 的 while 迴圈會在沒有新增票

30、數(shù)需要加總時, 讓負責加總的主流程等待, 等待的方式一樣是呼叫 Assistant 物件的 wait() 方法。一旦被喚醒繼續(xù)執(zhí)行時, 就會將 unprocessedData 設為 false, 並呼叫 Assistant 物件的 notify() 方法, 以便喚醒等待回報新增票數(shù)的執(zhí)行緒, 讓開票所能夠繼續(xù)回報新增票數(shù)。79協(xié)調(diào)執(zhí)行緒如此一來, 執(zhí)行結果就完全正確了:80協(xié)調(diào)執(zhí)行緒要注意的是, 對於物件 a 來說, 只有在物件 a 的 synchronized 方法或是以 a 為共享資源的 synchronized(a) 區(qū)塊中才能呼叫 a 的 wait() 方法。一旦執(zhí)行緒進入等待狀態(tài)時,

31、 就會暫時釋放其 synchronized 狀態(tài), 就好像這個執(zhí)行緒已經(jīng)離開 synchronized 方法或是區(qū)塊一樣, 讓其他的執(zhí)行緒可以呼叫同一方法或是進入同一區(qū)塊。81協(xié)調(diào)執(zhí)行緒等到其他執(zhí)行緒呼叫 notify() 喚醒等待的執(zhí)行緒時, 被喚醒的執(zhí)行緒必須等到可以重新進入 synchronized 狀態(tài)時才能繼續(xù)執(zhí)行。舉例來說, 如果您把 reportCount() 的 synchronized 字符拿掉:82協(xié)調(diào)執(zhí)行緒83協(xié)調(diào)執(zhí)行緒84協(xié)調(diào)執(zhí)行緒執(zhí)行時就會發(fā)生錯誤:85避免錯誤的 synchronized 寫法上一節(jié)曾經(jīng)提過使用 synchronized 來同步多個執(zhí)行緒, 不過要特

32、別注意的是, 使用 synchronized 標示方法時, 其實就等於是以物件自己為共享資源標示 synchronized 區(qū)塊, 也就是說, 以下的方法:86避免錯誤的 synchronized 寫法其實就等於:87避免錯誤的 synchronized 寫法因此, 如果同一類別中有多個 synchronized 方法時, 若是有執(zhí)行緒已進入此類別物件的某個 synchronized 方法, 就會造成其他執(zhí)行緒無法再呼叫同一物件的任一個 synchronized 方法。請看以下範例:88避免錯誤的 synchronized 寫法89避免錯誤的 synchronized 寫法90避免錯誤的 sy

33、nchronized 寫法91避免錯誤的 synchronized 寫法在這個程式中, Lock 類別有 2 個 synchronized 方法, 分別叫做 a() 與 b()。其中方法 a() 一執(zhí)行後就會進入一個 while 迴圈等待方法 b() 被執(zhí)行;而方法b() 則會設定 bExecuted 的值, 讓方法 a() 的 while 迴圈可以結束。92避免錯誤的 synchronized 寫法ThreadA 與 ThreadB 這兩個 Thread 子類別的 run() 方法則是分別呼叫 Lock.obj 這個物件的 a() 與 b() 方法。在 main() 方法中, 就分別產(chǎn)生了

34、ThreadA 與 ThreadB 的物件 ta 與 tb, 然後分別啟動執(zhí)行緒。理論上程式執(zhí)行後 ta 執(zhí)行緒會呼叫 Lock.obj.a() 而等待, 然後 tb 執(zhí)行緒會呼叫 Lock.obj.b() 解除 a() 的迴圈, 最後程式結束。不過實際的執(zhí)行結果卻是這樣:93避免錯誤的 synchronized 寫法ta 執(zhí)行緒一進入迴圈後就跑不出來了, 這表示 tb 執(zhí)行緒根本就沒有進入方法 b()。會造成這樣的結果, 就是因為方法 a() 與 b() 是同一個物件的 synchronized 方法, 當 ta 執(zhí)行緒進入方法 a() 後, 就導致 tb 執(zhí)行緒無法呼叫方法 b()。94避

35、免錯誤的 synchronized 寫法最後, 變成 ta 執(zhí)行緒要等 tb 執(zhí)行緒來解除它的迴圈, 可是 tb 執(zhí)行緒也在等 ta 執(zhí)行緒離開方法 a(), 彼此互相等待, 所以程式就停在方法 a() 中無法繼續(xù)了。像這樣多個執(zhí)行緒間相互等待的狀況, 就稱為死結 (Dead Lock)。如果將方法 b() 的 synchronized 字符拿掉, 程式就可以順利進行了:95避免錯誤的 synchronized 寫法96避免錯誤的 synchronized 寫法97避免錯誤的 synchronized 寫法如果這兩個方法都必須是 synchronized, 那麼就必須改用 wait() 來進行

36、等待。因為在上一小節(jié)提過, 當執(zhí)行緒呼叫某物件的 wait() 而進入等待狀態(tài)時, 會先釋放對於該物件的 synchronized 狀態(tài), 讓其它的執(zhí)行緒可以進入以同一物件為共享資源的 synchronized 區(qū)塊:98避免錯誤的 synchronized 寫法99避免錯誤的 synchronized 寫法要特別留意加上呼叫 notify() 的動作來解除等待狀態(tài), 這是撰寫多執(zhí)行緒程式時很容易疏忽的地方。10015-4 綜合演練用來通知計時結束的介面實作計時器類別測試 Timer 類別101用來通知計時結束的介面在第 12 章曾經(jīng)提過, 介面有一個用法就是提供給物件之間作為相互溝通的橋樑, 我們首先要實作的就是這樣的介面, 它可以讓計時器在計時結束時呼叫啟動該計時器的物件所提供的方法。102用來通知計時結束的介面要啟動計時器必須準備一個實作有 TimeUp 介面的物件, 並在啟動計時器時傳遞給計時器。當計時器計時結束時, 就會呼叫此 TimeUp 物件的 notify

溫馨提示

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

評論

0/150

提交評論