2009-04-14 85 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对加斯顿具有同步锁定。这两个线程现在卡住了,等待对方完成鞠躬才能够低头...