2009-04-14 84 views
11

首先,here's a sample在Java中如何同步工作

public class Deadlock { 
    static class Friend { 
     private final String name; 
     public Friend(String name) { 
      this.name = name; 
     } 
     public String getName() { 
      return this.name; 
     } 
     public synchronized void bow(Friend bower) { 
      System.out.format("%s: %s has bowed to me!%n", 
        this.name, bower.getName()); 
      bower.bowBack(this); 
     } 
     public synchronized void bowBack(Friend bower) { 
      System.out.format("%s: %s has bowed back to me!%n", 
        this.name, bower.getName()); 
     } 
    } 

    public static void main(String[] args) { 
     final Friend alphonse = new Friend("Alphonse"); 
     final Friend gaston = new Friend("Gaston"); 
     new Thread(new Runnable() { 
      public void run() { alphonse.bow(gaston); } 
     }).start(); 
     new Thread(new Runnable() { 
      public void run() { gaston.bow(alphonse); } 
     }).start(); 
    } 
} 

什麼我不明白是怎麼發生堵塞。主函數啓動兩個線程,每個線程開始自己的弓。

'synchronized'block究竟是什麼?運行同一個對象的相同功能(正如我原先所想的那樣)?同一個類的所有對象都具有相同的功能?同一個對象的所有同步函數?所有同一類的所有對象的同步函數?

幫我看看這裏。

回答

22

在Java中,每個Object都提供了一個線程到synchronize或鎖定的能力。當方法同步時,該方法使用其對象實例作爲鎖。在你的例子中,方法bowbowBack都是​​,並且都在同一類Friend。這意味着執行這些方法的任何線程都將在Friend實例上同步它的鎖。

,這將導致死鎖事件的順序是:

  1. 第一個線程調用開始alphonse.bow(gaston),這是​​的alphonseFriend對象。這意味着線程必須從這個對象獲取鎖。
  2. 第二個線程開始調用gaston.bow(alphonse),這是​​上的gastonFriend對象。這意味着線程必須從該對象獲取鎖。
  3. 現在開始的第一個線程調用bowback並等待gaston的鎖被釋放。
  4. 現在開始的第二個線程調用bowback並等待alphonse的鎖被釋放。

要顯示的事件序列中更多的細節:

  1. main()開始在主Therad執行(稱之爲線程#1),創建了兩個Friend實例。到現在爲止還挺好。
  2. 主線程以代碼new Thread(new Runnable() { ...開始其第一個新線程(稱爲線程#2)。線程#2調用alphonse.bow(gaston),這是​​上的alphonseFriend對象。線程#2從而獲取alphonse對象的「鎖定」並輸入bow方法。
  3. 時間切片發生在這裏,原始線程有機會做更多的處理。
  4. 主線程啓動第二個新線程(稱爲線程#3),就像第一個線程一樣。線程#3調用gaston.bow(alphonse),它在gastonFriend對象上同步。由於沒有人已獲得對象實例的「鎖定」,因此線程#3成功獲取此鎖並輸入bow方法。
  5. 時間片發生在這裏,線程#2有機會做更多的處理。
  6. 線程#2現在調用bower.bowBack(this);bower是對gaston的實例的引用。這是gaston.bowBack(alphonse)調用的邏輯等價物。因此,該方法在gaston實例上爲​​。此對象的鎖已被獲取並由另一個線程(線程#3)保存。因此,線程#2必須等待gaston上的鎖被釋放。線程處於等待狀態,允許線程3進一步執行。
  7. 線程#3現在調用bowback,在這種情況下,它在邏輯上與調用alphonse.bowBack(gaston)相同。爲此,它需要獲取alphonse實例的鎖,但此鎖由線程#2保存。此線程現在處於等待狀態。

而你現在處於一個線程無法執行的位置。線程#2和線程#3都在等待鎖釋放。但是沒有一個線程正在進行,這兩個鎖都不能被釋放。但是如果沒有釋放鎖,這兩個線程都不能取得進展。

因此:死鎖!

死鎖通常取決於發生的特定事件序列,由於它們很難再現,因此很難調試。

+2

噢好吧。所以鎖屬於整個對象。我不知道爲什麼我認爲它只是調用同一個被阻塞的給定對象的同步方法。 我想那回答我的問題。 – 2009-04-15 00:04:32

2

Synchronized has two effects

  • 首先,它是不可能的同一對象上同步方法來交錯兩個調用。當一個線程正在執行一個對象的同步方法時,所有其他線程調用同一對象的同步方法塊(掛起執行),直到第一個線程完成對象。其次,當一個同步方法退出時,它會自動建立一個與先前同步對象的任何後續調用同步方法的before-before關係。這保證了對所有線程都可見的對象狀態的更改。

因此,簡而言之,它會阻止同一對象上的同步方法的任何調用。

2

同一對象的所有同步函數。標記一個「synchronized」方法與在方法的整個內容中放置一個「synchronized(this){」塊非常相似。我不說「相同」的原因是因爲我不知道編譯器是否發出相同的字節碼,但AFAIK定義的運行時效果是相同的。

死鎖是一種經典的鎖定反轉。一個線程鎖定alphonse。然後(或同時在多核系統上)另一個線程鎖定gaston。這部分要求線程的調度恰好恰好在正確的點交叉。

然後,每個線程(以任何順序或同時)嘗試獲取已被其他線程佔用的鎖,因此每個線程都會進入睡眠狀態。直到另一個釋放它的鎖,它們都不會喚醒,但它們都不會在它醒來(或終止)之前釋放它的鎖。

2

同步的方法是相同的包圍所有這些方法的代碼成

synchronized(this) { 
    /// code here ... 
} 

塊。

對於給定的對象實例ø,只有一次一個線程可以運行任何同步(O)塊。每個其他線程都試圖嚎,大哭,直到運行該塊的線程(其上有同步鎖)退出該塊(放棄鎖)。

就你而言,當Alphonse開始在線程1中鞠躬時發生死鎖,從而進入同步塊。線程1然後被系統換出,所以線程2可以開始,並讓Gaston鞠躬。但是加斯頓還不能退縮,因爲它在阿爾方斯上同步,線程1已經擁有了這個鎖。它將因此等待線程1離開該塊。然後系統將交換線程1,這會嘗試讓Alphonse向後退。除非它不能這樣做,因爲線程2對加斯頓具有同步鎖定。這兩個線程現在卡住了,等待對方完成鞠躬才能夠低頭...