?? 以全局的固定順序獲取多個(gè)鎖來(lái)避免死鎖.txt
字號(hào):
以全局的固定順序獲取多個(gè)鎖來(lái)避免死鎖
(加入日期:2001-5-6 點(diǎn)擊數(shù):710)
【對(duì)此文發(fā)表評(píng)論】 【編程愛好者論壇】 【保存文章至硬盤】 【打印文章】
當(dāng)兩個(gè)或多個(gè)線程互相等待時(shí)被阻塞,就會(huì)發(fā)生死鎖。例如,第一個(gè)線程被第二個(gè)線程阻塞,它在等待第二個(gè)線程持有的一個(gè)資源。而第二個(gè)線程在獲得第一個(gè)線程持有的某個(gè)資源之前不會(huì)釋放這個(gè)資源。由于第一個(gè)線程在獲得第二個(gè)線程持有的那個(gè)資源之前不會(huì)釋放它自己所持有的資源,而第二個(gè)線程在獲得第一個(gè)線程持有的一個(gè)資源之前也不會(huì)釋放它所持有的資源,于是這兩個(gè)線程就被死鎖。
在編寫多線程代碼時(shí),死鎖是最難處理的問(wèn)題之一。因?yàn)樗梨i可能在最意想不到的地方發(fā)生,所以查找和修正它既費(fèi)時(shí)又費(fèi)力。例如,試考慮下面這段鎖定了多個(gè)對(duì)象的代碼。
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<size; i++)
value += a1[i] + a2[i];
}
}
}
return value;
}
這段代碼在求和操作中訪問(wèn)兩個(gè)數(shù)組對(duì)象之前正確地鎖定了這兩個(gè)數(shù)組對(duì)象。它形式簡(jiǎn)短,編寫也適合所要執(zhí)行的任務(wù);但不幸的是,它有一個(gè)潛在的問(wèn)題。這個(gè)問(wèn)題就是它埋下了死鎖的種子,除非您在不同的線程中對(duì)相同的對(duì)象調(diào)用該方法時(shí)格外小心。要查看潛在的死鎖,請(qǐng)考慮如下的事件序列:
創(chuàng)建兩個(gè)數(shù)組對(duì)象,ArrayA 和 ArrayB。
線程 1 用下面的調(diào)用來(lái)調(diào)用 sumArrays 方法:
sumArrays(ArrayA, ArrayB);
線程 2 用下面的調(diào)用來(lái)調(diào)用 sumArrays 方法:
sumArrays(ArrayB, ArrayA);
線程 1 開始執(zhí)行 sumArrays 方法并在 //1 處獲得對(duì)參數(shù) a1 的鎖,對(duì)于這個(gè)調(diào)用而言,它就是對(duì) ArrayA 對(duì)象的鎖。
然后在 //2 處,在線程 1 獲得對(duì) ArrayB 的鎖之前被搶先。
線程 2 開始執(zhí)行 sumArrays 方法并在 //1 處獲得對(duì)參數(shù) a1 的鎖,對(duì)于這個(gè)調(diào)用而言,它就是對(duì) ArrayB 對(duì)象的鎖。
然后線程 2 在 //2 處試圖獲取對(duì)參數(shù) a2 的鎖,它是對(duì) ArrayA 對(duì)象的鎖。因?yàn)檫@個(gè)鎖當(dāng)前由線程 1 持有,所以線程 2 被阻塞。
線程 1 開始執(zhí)行并在 //2 處試圖獲取對(duì)參數(shù) a2 的鎖,它是對(duì) ArrayB 對(duì)象的鎖。因?yàn)檫@個(gè)鎖當(dāng)前由線程 2 持有,所以線程 1 被阻塞。
現(xiàn)在兩個(gè)線程都被死鎖。
避免這種問(wèn)題的一種方法是讓代碼按固定的全局順序獲取鎖。在本例中,如果線程 1 和線程 2 按相同的順序?qū)?shù)調(diào)用 sumArrays 方法,就不會(huì)發(fā)生死鎖。但是,這一技術(shù)要求,多線程代碼的程序員在調(diào)用那些鎖定作為參數(shù)傳入的對(duì)象的方法時(shí)需要格外小心。在您遇到這種死鎖并不得不進(jìn)行調(diào)試之前,使用這一技術(shù)的應(yīng)用程序似乎不切實(shí)際。
另外,您也可以將鎖定順序嵌入對(duì)象的內(nèi)部。這允許代碼查詢它準(zhǔn)備為其獲得鎖的對(duì)象,以確定正確的鎖定順序。只要即將鎖定的所有對(duì)象都支持鎖定順序表示法,并且獲取鎖的代碼遵循這一策略,就可避免這種潛在死鎖的情況。
在對(duì)象中嵌入鎖定順序的缺點(diǎn)是,這種實(shí)現(xiàn)將使內(nèi)存需求和運(yùn)行時(shí)成本增加。另外,在上例中應(yīng)用這一技術(shù)需要在數(shù)組中有一個(gè)包裝對(duì)象,用來(lái)存放鎖定順序信息。例如,試考慮下面的代碼,它由前面的示例修改而來(lái),其中實(shí)現(xiàn)了鎖定順序技術(shù):
class ArrayWithLockOrder
{
private static long num_locks = 0;
private long lock_order;
private int[] arr;
public ArrayWithLockOrder(int[] a)
{
arr = a;
synchronized(ArrayWithLockOrder.class) {
num_locks++; // 鎖數(shù)加 1。
lock_order = num_locks; // 為此對(duì)象實(shí)例設(shè)置唯一的 lock_order。
}
}
public long lockOrder()
{
return lock_order;
}
public int[] array()
{
return arr;
}
}
class SomeClass implements Runnable
{
public int sumArrays(ArrayWithLockOrder a1,
ArrayWithLockOrder a2)
{
int value = 0;
ArrayWithLockOrder first = a1; // 保留數(shù)組引用的一個(gè)
ArrayWithLockOrder last = a2; // 本地副本。
int size = a1.array().length;
if (size == a2.array().length)
{
if (a1.lockOrder() > a2.lockOrder()) // 確定并設(shè)置對(duì)象的鎖定
{ // 順序。
first = a2;
last = a1;
}
synchronized(first) { // 按正確的順序鎖定對(duì)象。
synchronized(last) {
int[] arr1 == a1.array();
int[] arr2 == a2.array();
for (int i=0; i<size; i++)
value += arr1[i] + arr2[i];
}
}
}
return value;
}
public void run() {
//...
}
}
在第一個(gè)示例中,ArrayWithLockOrder 類是作為數(shù)組的一個(gè)包裝提供的。每創(chuàng)建該類的一個(gè)新對(duì)象,該類就將 static num_locks 變量加 1。一個(gè)單獨(dú)的 lock_order 實(shí)例變量被設(shè)置為 num_locks static 變量的當(dāng)前值。這可以保證,對(duì)于該類的每個(gè)對(duì)象,lock_order 變量都有一個(gè)獨(dú)特的值。lock_order 實(shí)例變量充當(dāng)此對(duì)象相對(duì)于該類的其他對(duì)象的鎖定順序指示器。
請(qǐng)注意,static num_locks 變量是在 synchronized 語(yǔ)句中進(jìn)行操作的。這是必須的,因?yàn)閷?duì)象的每個(gè)實(shí)例共享該對(duì)象的 static 變量。因此,當(dāng)兩個(gè)線程同時(shí)創(chuàng)建 ArrayWithLockOrder 類的一個(gè)對(duì)象時(shí),如果操作 static num_locks 變量的代碼未作同步處理,該變量就可能被破壞。對(duì)此代碼作同步處理可以保證,對(duì)于 ArrayWithLockOrder 類的每個(gè)對(duì)象,lock_order 變量都有一個(gè)獨(dú)特的值。
此外還更新了 sumArrays 方法,以使它包括確定正確鎖定順序的代碼。在請(qǐng)求鎖之前,將查詢每個(gè)對(duì)象以獲得它的鎖定順序。編號(hào)較小的首先被鎖定。此代碼可以保證,不管各對(duì)象是以什么順序傳給此方法,它們總是被以相同的順序鎖定。
static num_locks 域和 lock_order 域都是作為 long 類型實(shí)現(xiàn)的。long 數(shù)據(jù)類型是作為 64 位有符號(hào)二進(jìn)制補(bǔ)碼整數(shù)實(shí)現(xiàn)的。這意味著在創(chuàng)建 9,223,372,036,854,775,807 個(gè)對(duì)象之后,num_locks 和 lock_order 的值將重新開始。您未必會(huì)達(dá)到這個(gè)極限,但在適當(dāng)?shù)臈l件下這是可能發(fā)生的。
實(shí)現(xiàn)嵌入的鎖定順序需要投入更多的工作,使用更多的內(nèi)存,并會(huì)延長(zhǎng)執(zhí)行時(shí)間。但是,如果您的代碼中可能存在這些類型的死鎖,您也許會(huì)發(fā)現(xiàn)值得這樣做。如果您無(wú)法承受額外的內(nèi)存和執(zhí)行開銷,或者不能接受 num_locks 或 lock_order 域重新開始的可能性,則您在建立鎖定對(duì)象的預(yù)定義順序時(shí)應(yīng)該仔細(xì)斟酌。
本欄文章均來(lái)自于互聯(lián)網(wǎng),版權(quán)歸原作者和各發(fā)布網(wǎng)站所有,本站收集這些文章僅供學(xué)習(xí)參考之用。任何人都不能將這些文章用于商業(yè)或者其他目的。( ProgramFan.Com )
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -