練習 1.4:解決執行緒瓶頸
開始之前,您必須先完成練習 1.3:識別執行緒瓶頸。
若要尋找程式碼中的死結,除了「執行緒視圖」,
您還可以使用「UML2 序列圖」(「物件互動」及「執行緒互動」視圖)以及「呼叫堆疊視圖」。
為了解決這個死結,讓我們先查出問題中所涉及的方法呼叫和物件:
- 在「執行緒視圖」中,
尋找第一個進入「死結」狀態的 philo* 執行緒。將游標暫停在「死結」區段上。工具要訣會識別鎖定(分出.<ID 號碼>)及鎖定的執行緒(鎖定的執行緒.<名稱>),例如:
鎖定:分出 10038
鎖定的執行緒:philo#1
- 用滑鼠右鍵按一下此程式執行的側寫資源,
然後選取開啟工具 > UML2 物件互動。這時會開啟「UML2 序列圖」視圖,顯示物件互動。
- 在序列圖中,水平捲動視圖,以尋找分出.<ID 號碼>,再按一下將它選取。

- 向下捲動,在其中一個 philo* 執行緒至分出.<ID 號碼> 之間尋找水平箭頭。這些箭頭會顯示物件之間的互動,
並且兩個物件之間的第一個互動會指出 philo* 執行緒已獲得分出.<ID 號碼>。您將會發現含 getName 標籤的箭頭。

- 按一下 getName。在「執行緒視圖」中,
垂直的「現行時間」指標會移動,
以顯示 getName 被呼叫時整個程式中的發生情況。您會看到要求已順利完成,
因為提出要求的 philo* 執行緒尚未處於「等待鎖定」或「死結」狀態。

- 在序列圖中,再次按一下分出.<ID 號碼>,然後向下捲動,
以尋找源自不同 philo* 執行緒的 getName 要求。
- 按一下這個 getName 實例。「現行時間」指標會顯示,
提出要求的 philo* 執行緒未取得分出.<ID 號碼>,
而是進入「等待鎖定」,接著進入「死結」狀態。
在這個實例中,對另一個執行緒所持有之分出的要求是問題所在。
檢查其他處於死結狀態的執行緒,以驗證其他實例中是否也是這個狀況。
現在,讓我們尋找造成問題的方法:
- 用滑鼠右鍵按一下此程式執行的側寫資源,
然後選取開啟工具 > UML2 執行緒互動。這時會開啟「UML2 序列圖」視圖,顯示執行緒互動。
- 在「執行緒視圖」中,按一下功能表下拉按鈕,
再按一下開啟呼叫堆疊視圖。
- 在顯示執行緒名稱的「執行緒視圖」中,
按兩下第一個在死結狀態的 philo* 執行緒。請注意,「執行緒互動」視圖會變更成只顯示該執行緒的資訊。
- 向下捲動至執行緒資訊結尾,
然後按兩下執行緒執行的最後一個方法:run 方法。「呼叫堆疊視圖」會顯示當時在堆疊上的所有呼叫。
- 在「呼叫堆疊」中,
請注意,持有鎖定的執行緒已呼叫 Philosopher.java 中的 Sleep 方法;
或者它也是處於死結狀態,因此不會執行任何動作。
- 檢查在程式執行結束之後處於死結狀態的其他執行緒;
Philosopher.java 中的 Sleep 方法常出現在「呼叫堆疊」中,可能是問題所在。
我們現在懷疑 Sleep 方法是問題所在。讓我們查看程式碼:
- 在「呼叫堆疊」中,用滑鼠右鍵按一下 Sleep(int) void [Philosopher.java] 實例,
然後選取開啟程式碼。在編輯器中會開啟程式碼,並移到 Sleep 類別的位置。
- 查驗程式碼。請注意,Sleep 方法是由 run 方法所呼叫的。首先會呼叫 trace,
它會印出訊息 "got left...",然後再呼叫 Sleep。透過註銷對 Sleep 的呼叫,我們可能可以防止死結。
- 註銷 Sleep。
- 選取檔案 > 儲存。
現在,再次側寫程式。
這次,程式執行就不會發生死結,並且會寫入主控台:
HeadWaiter reports all philosophers have finished dining normally
您可以清楚看到,「執行緒視圖」和其他視圖會顯示程式執行時執行緒的發生情況。您可以根據對程式的知識,
自行決定是否要執行分析並解決死結。
請檢視摘要中的資料來完成您的指導教學。