巢狀管程鎖死

2021-08-20 05:50:37 字數 4282 閱讀 2500

譯者:余紹亮校對:丁一

巢狀管程鎖死類似於死鎖, 下面是乙個巢狀管程鎖死的場景:

執行緒1獲得a物件的鎖。

執行緒1獲得物件b的鎖(同時持有物件a的鎖)。

執行緒1決定等待另乙個執行緒的訊號再繼續。

執行緒1呼叫b.wait(),從而釋放了b物件上的鎖,但仍然持有物件a的鎖。

執行緒2需要同時持有物件a和物件b的鎖,才能向執行緒1發訊號。

執行緒2無法獲得物件a上的鎖,因為物件a上的鎖當前正被執行緒1持有。

執行緒2一直被阻塞,等待執行緒1釋放物件a上的鎖。

執行緒1一直阻塞,等待執行緒2的訊號,因此,不會釋放物件a上的鎖,

而執行緒2需要物件a上的鎖才能給執行緒1發訊號……

你可以能會說,這是個空想的場景,好吧,讓我們來看看下面這個比較挫的lock實現:

01//lock implementation with nested monitor lockout problem

02publicclasslock

12}

13islocked =true;

14}

15}

16

17publicvoidunlock()

23}

24}

25}

可以看到,lock()方法首先在」this」上同步,然後在monitorobject上同步。如果islocked等於false,因為執行緒不會繼續呼叫monitorobject.wait(),那麼一切都沒有問題 。但是如果islocked等於true,呼叫lock()方法的執行緒會在monitorobject.wait()上阻塞。

這裡的問題在於,呼叫monitorobject.wait()方法只釋放了monitorobject上的管程物件,而與」this「關聯的管程物件並沒有釋放。換句話說,這個剛被阻塞的執行緒仍然持有」this」上的鎖。

(校對注:如果乙個執行緒持有這種lock的時候另乙個執行緒執行了lock操作)當乙個已經持有這種lock的執行緒想呼叫unlock(),就會在unlock()方法進入synchronized(this)塊時阻塞。這會一直阻塞到在lock()方法中等待的執行緒離開synchronized(this)塊。但是,在unlock中islocked變為false,monitorobject.notify()被執行之後,lock()中等待的執行緒才會離開synchronized(this)塊。

簡而言之,在lock方法中等待的執行緒需要其它執行緒成功呼叫unlock方法來退出lock方法,但是,在lock()方法離開外層同步塊之前,沒有執行緒能成功執行unlock()。

結果就是,任何呼叫lock方法或unlock方法的執行緒都會一直阻塞。這就是巢狀管程鎖死。

乙個更現實的例子

你可能會說,這麼挫的實現方式我怎麼可能會做呢?你或許不會在裡層的管程物件上呼叫wait或notify方法,但完全有可能會在外層的this上調。

有很多類似上面例子的情況。例如,如果你準備實現乙個公平鎖。你可能希望每個執行緒在它們各自的queueobject上呼叫wait(),這樣就可以每次喚醒乙個執行緒。

下面是乙個比較挫的公平鎖實現方式:

01//fair lock implementation with nested monitor lockout problem

02publicclassfairlockcatch(interruptedexception e)

24}

25}

26waitingthreads.remove(queueobject);

27islocked =true;

28lockingthread = thread.currentthread();

29}

30}

31

32publicsynchronizedvoidunlock()

37islocked =false;

38lockingthread =null;

39if(waitingthreads.size() >0)

44}

45}

46}

1publicclassqueueobject {}

乍看之下,嗯,很好,但是請注意lock方法是怎麼呼叫queueobject.wait()的,在方法內部有兩個synchronized塊,乙個鎖定this,乙個嵌在上乙個synchronized塊內部,它鎖定的是區域性變數queueobject。

當乙個執行緒呼叫queueobject.wait()方法的時候,它僅僅釋放的是在queueobject物件例項的鎖,並沒有釋放」this」上面的鎖。

現在我們還有乙個地方需要特別注意, unlock方法被宣告成了synchronized,這就相當於乙個synchronized(this)塊。這就意味著,如果乙個執行緒在lock()中等待,該執行緒將持有與this關聯的管程物件。所有呼叫unlock()的執行緒將會一直保持阻塞,等待著前面那個已經獲得this鎖的執行緒釋放this鎖,但這永遠也發生不了,因為只有某個執行緒成功地給lock()中等待的執行緒傳送了訊號,this上的鎖才會釋放,但只有執行unlock()方法才會傳送這個訊號。

因此,上面的公平鎖的實現會導致巢狀管程鎖死。更好的公平鎖實現方式可以參考starvation and fairness。

巢狀管程鎖死 vs 死鎖

巢狀管程鎖死與死鎖很像:都是執行緒最後被一直阻塞著互相等待。

但是兩者又不完全相同。在死鎖中我們已經對死鎖有了個大概的解釋,死鎖通常是因為兩個執行緒獲取鎖的順序不一致造成的,執行緒1鎖住a,等待獲取b,執行緒2已經獲取了b,再等待獲取a。如死鎖避免中所說的,死鎖可以通過總是以相同的順序獲取鎖來避免。

但是發生巢狀管程鎖死時鎖獲取的順序是一致的。執行緒1獲得a和b,然後釋放b,等待執行緒2的訊號。執行緒2需要同時獲得a和b,才能向執行緒1傳送訊號。所以,乙個執行緒在等待喚醒,另乙個執行緒在等待想要的鎖被釋放。

不同點歸納如下:

死鎖中,二個執行緒都在等待對方釋放鎖。

巢狀管程鎖死中,執行緒1持有鎖a,同時等待從執行緒2發來的訊號,執行緒2需要鎖a來發訊號給執行緒1。

巢狀管程鎖死

巢狀管程鎖死

巢狀管程鎖死類似於死鎖,下面是乙個巢狀管程鎖死的場景 執行緒1獲得a物件的鎖。執行緒1獲得物件b的鎖 同時持有物件a的鎖 執行緒1決定等待另乙個執行緒的訊號再繼續。執行緒1呼叫b.wait 從而釋放了b物件上的鎖,但仍然持有物件a的鎖。執行緒2需要同時持有物件a和物件b的鎖,才能向執行緒1發訊號。執...

16巢狀管程鎖死

巢狀管程鎖死類似於死鎖,下面是乙個巢狀管程鎖死的場景 執行緒1獲得a物件的鎖。執行緒1獲得物件b的鎖 同時持有物件a的鎖 執行緒1決定等待另乙個執行緒的訊號再繼續。執行緒1呼叫b.wait 從而釋放了b物件上的鎖,但仍然持有物件a的鎖。執行緒2需要同時持有物件a和物件b的鎖,才能向執行緒1發訊號。執...

java併發(十三)巢狀管程鎖死

巢狀管程鎖死類似於死鎖,下面是乙個巢狀管程鎖死的場景 quote 執行緒1獲得a物件的鎖。執行緒1獲得物件b的鎖 同時持有物件a的鎖 執行緒1決定等待另乙個執行緒的訊號再繼續。執行緒1呼叫b.wait 從而釋放了b物件上的鎖,但仍然持有物件a的鎖。執行緒2需要同時持有物件a和物件b的鎖,才能向執行緒...