當兩個或多個執行緒互相等待時被阻塞,就會發生死鎖。例如,第乙個執行緒被第二個執行緒阻塞,它在等待第二個執行緒持有的乙個資源。而第二個執行緒在獲得第乙個執行緒持有的某個資源之前不會釋放這個資源。由於第乙個執行緒在獲得第二個執行緒持有的那個資源之前不會釋放它自己所持有的資源,而第二個執行緒在獲得第乙個執行緒持有的乙個資源之前也不會釋放它所持有的資源,於是這兩個執行緒就被死鎖。
在編寫多執行緒**時,死鎖是最難處理的問題之一。因為死鎖可能在最意想不到的地方發生,所以查詢和修正它既費時又費力。例如,試考慮下面這段鎖定了多個物件的**。
public int sumarrays(int a1, int a2)
{int value = 0;
int size = a1.length;
if (size == a2.length) {
synchronized(a1) { //1
synchronized(a2) { //2
for (int i=0; i
這段**在求和操作中訪問兩個陣列物件之前正確地鎖定了這兩個陣列物件。它形式簡短,編寫也適合所要執行的任務;但不幸的是,它有乙個潛在的問題。這個問題就是它埋下了死鎖的種子,除非您在不同的執行緒中對相同的物件呼叫該方法時格外小心。要檢視潛在的死鎖,請考慮如下的事件序列:
建立兩個陣列物件,arraya
和arrayb
。
執行緒 1 用下面的呼叫來呼叫sumarrays
方法:
sumarrays(arraya, arrayb);
執行緒 2 用下面的呼叫來呼叫sumarrays
方法:
sumarrays(arrayb, arraya);
執行緒 1 開始執行sumarrays
方法並在 //1 處獲得對引數 a1 的鎖,對於這個呼叫而言,它就是對arraya
物件的鎖。
執行緒 2 開始執行sumarrays
方法並在 //1 處獲得對引數 a1 的鎖,對於這個呼叫而言,它就是對arrayb
物件的鎖。
然後執行緒 2 在 //2 處試圖獲取對引數 a2 的鎖,它是對arraya
物件的鎖。因為這個鎖當前由執行緒 1 持有,所以執行緒 2 被阻塞。
執行緒 1 開始執行並在 //2 處試圖獲取對引數 a2 的鎖,它是對arrayb
物件的鎖。因為這個鎖當前由執行緒 2 持有,所以執行緒 1 被阻塞。
現在兩個執行緒都被死鎖。
避免這種問題的一種方法是讓**按固定的全域性順序獲取鎖。在本例中,如果執行緒 1 和執行緒 2 按相同的順序對引數呼叫sumarrays
方法,就不會發生死鎖。但是,這一技術要求,多執行緒**的程式設計師在呼叫那些鎖定作為引數傳入的物件的方法時需要格外小心。在您遇到這種死鎖並不得不進行除錯之前,使用這一技術的應用程式似乎不切實際。
另外,您也可以將鎖定順序嵌入物件的內部。這允許**查詢它準備為其獲得鎖的物件,以確定正確的鎖定順序。只要即將鎖定的所有物件都支援鎖定順序表示法,並且獲取鎖的**遵循這一策略,就可避免這種潛在死鎖的情況。
在物件中嵌入鎖定順序的缺點是,這種實現將使記憶體需求和執行時成本增加。另外,在上例中應用這一技術需要在陣列中有乙個包裝物件,用來存放鎖定順序資訊。例如,試考慮下面的**,它由前面的示例修改而來,其中實現了鎖定順序技術:
在第乙個示例中,arraywithlockorder
類是作為陣列的乙個包裝提供的。每建立該類的乙個新物件,該類就將static num_locks
變數加 1。乙個單獨的lock_order
例項變數被設定為num_locks
static
變數的當前值。這可以保證,對於該類的每個物件,lock_order
變數都有乙個獨特的值。lock_order
例項變數充當此物件相對於該類的其他物件的鎖定順序指示器。
請注意,static
num_locks
變數是在synchronized
語句中進行操作的。這是必須的,因為物件的每個例項共享該物件的static
變數。因此,當兩個執行緒同時建立arraywithlockorder
類的乙個物件時,如果操作static
num_locks
變數的**未作同步處理,該變數就可能被破壞。對此**作同步處理可以保證,對於arraywithlockorder
類的每個物件,lock_order
變數都有乙個獨特的值。
此外還更新了sumarrays
方法,以使它包括確定正確鎖定順序的**。在請求鎖之前,將查詢每個物件以獲得它的鎖定順序。編號較小的首先被鎖定。此**可以保證,不管各物件是以什麼順序傳給此方法,它們總是被以相同的順序鎖定。
static num_locks
域和lock_order
域都是作為long
型別實現的。long
資料型別是作為 64 位有符號二進位制補碼整數實現的。這意味著在建立 9,223,372,036,854,775,807 個物件之後,num_locks
和lock_order
的值將重新開始。您未必會達到這個極限,但在適當的條件下這是可能發生的。
實現嵌入的鎖定順序需要投入更多的工作,使用更多的記憶體,並會延長執行時間。但是,如果您的**中可能存在這些型別的死鎖,您也許會發現值得這樣做。如果您無法承受額外的記憶體和執行開銷,或者不能接受num_locks
或lock_order
域重新開始的可能性,則您在建立鎖定物件的預定義順序時應該仔細斟酌。
以全域性的固定順序獲取多個鎖來
當兩個或多個執行緒互相等待時被阻塞,就會發生死鎖。例如,第乙個執行緒被第二個執行緒阻塞,它在等待第二個執行緒持有的乙個資源。而第二個執行緒在獲得第乙個執行緒持有的某個資源之前不會釋放這個資源。由於第乙個執行緒在獲得第二個執行緒持有的那個資源之前不會釋放它自己所持有的資源,而第二個執行緒在獲得第乙個執...
以全域性的固定順序獲取多個鎖來避免死鎖
當兩個或多個執行緒互相等待時被阻塞,就會發生死鎖。例如,第乙個執行緒被第二個執行緒阻塞,它在等待第二個執行緒持有的乙個資源。而第二個執行緒在獲得第乙個執行緒持有的某個資源之前不會釋放這個資源。由於第乙個執行緒在獲得第二個執行緒持有的那個資源之前不會釋放它自己所持有的資源,而第二個執行緒在獲得第乙個執...
以乙個固定 全域性次序獲取多個鎖
當兩個或多個執行緒互相等待時被阻塞,就會發生死鎖。例如,第乙個執行緒被第二個執行緒阻塞,它在等待第二個執行緒持有的乙個資源。而第二個執行緒在獲得第乙個執行緒持有的某個資源之前不會釋放這個資源。由於第乙個執行緒在獲得第二個執行緒持有的那個資源之前不會釋放它自己所持有的資源,而第二個執行緒在獲得第乙個執...