2016-10-25 67 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