2016-10-25 63 views
2

我在Oracle的Java教程中遇到了這個example,它描述了多線程場景中的死鎖。使用System.out.format和System.out.println進行多線程處理

因此,在這個例子中我提出以下在第17行的變化和線18

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) { 
     //My Changes 
     //System.out.format("%s: %s" + " has bowed to me!%n", this.name, bower.getName()); //Line 17 
     System.out.println(this.name + ": " + bower.getName() + " has bowed to me!"); //Line 18 
     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() { 
     @Override 
     public void run() { 
      alphonse.bow(gaston); 
     } 
    }).start(); 

    new Thread(new Runnable() { 
     @Override 
     public void run() { 
      gaston.bow(alphonse); 
     } 
    }).start(); 
    } 
} 

在這樣做這些改變,而不會引起死鎖成功地終止該程序和打印輸出如下

Alphonse: Gaston has bowed to me! 
Gaston: Alphonse has bowed back to me! 
Gaston: Alphonse has bowed to me! 
Alphonse: Gaston has bowed back to me! 

因此,我問題是 - 爲什麼它的行爲如此? println聲明如何防止死鎖?

+0

我看不出它會如何。 'System.out.format'在鎖定方面與'System.out.println'不同。 –

+2

它沒有區別 - 只是死鎖取決於線程交錯,這在運行之間會有所不同。如果使用System.format多次運行它,您可能會不時觀察到正確的輸出。使用println,您還會看到程序死鎖的運行。 – assylias

+0

確實:第一個變體[可以在ideone上正常運行](http://ideone.com/bV6nd8)。 –

回答

5

您是否使用System.out.printSystem.out.format沒有任何區別:它們基本上是做同樣的事情。

如果Gaston.bow(Alphonse)執行的Alphonse.bow(Gaston)bower.bowBack(Alphonse)(或反之亦然)的開始之間開始死鎖這裏發生:兩個線程正在等待由其它保持的顯示器,並且因此發生死鎖。

這種情況不一致,因爲它依賴於一個微妙的時機的問題,這取決於線程是如何安排 - 這是可能的,Alphonse.bowbower.backBack(Alphonse)完整Gaston.bow之前執行,所以看起來沒有僵局。

解決這個問題的經典方法是命令鎖獲取,以便每次首先獲取同一個鎖;這樣可以防止死鎖的可能性:

public void bow(Friend bower) { // Method no longer synchronized. 
    int firstHash = System.identityHashCode(this); 
    int secondHash = System.identityHashCode(bower); 

    Object firstMonitor = firstHash < secondHash ? this : bower; 
    Object secondMonitor = firstHash < secondHash ? bower : this; 
    synchronized (firstMonitor) { 
    synchronized (secondMonitor) { 
     // Code free (*) of deadlocks, with respect to this and bower at least. 
    } 
    } 
} 

(*)這不是相當保證是無死鎖的,因爲System.identityHashCode可以返回針對不同的對象相同的值;但這是不太可能的。

這是Birthday paradox的一個應用:如果你只有兩個顯示器,碰撞的機會就像10^-18;但如果你的顯示器超過77k,碰撞可能性就不大。

4

你在這裏混合的東西。

那一段代碼可以導致成僵局情況並不一定意味着你收到的代碼運行任何時候每一個僵局,這一事實。

這是使多線程如此困難的主題之一:如果你運行你的代碼一次,或10次,或100次,並且一切都「有效」;它仍然有可能會在下一次失敗。換句話說:嘗試將代碼放在最外層的循環中,並且遲早(可能更快;如果你沒有多少「睡覺」),你應該打開僵局!

如果事情這麼簡單和死鎖可以檢測容易,我們就不需要那些書和圖書館和想法如何處理多線程...

1

僵局是不依賴於println函數。這是由兩個線程試圖訪問對方,並相互鎖定引起的。

從格式到println的更改將在程序中引入足夠的延遲,以允許線程彼此鎖定而不會發生碰撞,即死鎖。所以你沒有真正解決它;你剛剛添加了一些延遲,這意味着線程不會死鎖。

3

爲了在這裏支持其他的答案和一些實際證明,我在一個循環中運行了你的代碼,它在100次嘗試中死鎖了82次,所以你的代碼絕對是死鎖。

+0

我不同意這一點。問題是「println聲明如何防止僵局?」我的回答是它沒有。我可以同意,它不能作爲自己的答案,雖然因爲我指的是其他答案 – Raniz

+0

有道理;-) – GhostCat