如何在Java中同步工作

时间:2009-04-14 22:49:42

标签: java multithreading deadlock

首先,here's a sample

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();
    }
}

我得不到的是 阻塞是如何发生的。 main函数启动两个线程,每个线程都开始自己的弓箭。

'同步'到底有什么阻碍?为同一个对象运行相同的函数(我原来认为)?同一个类的所有对象的相同功能?同一对象的所有同步函数?同一类的所有对象的所有同步函数?

帮助我。

4 个答案:

答案 0 :(得分:26)

在Java中,每个Object都为线程提供synchronize或锁定它的能力。方法同步时,该方法使用其对象实例作为锁。在您的示例中,方法bowbowBack都是synchronized,并且两者都在同一个类Friend中。这意味着执行这些方法的任何线程都将在Friend实例上同步锁定。

导致死锁的一系列事件是:

  1. 第一个线程开始调用alphonse.bow(gaston)synchronized alphonse对象上的Friend。这意味着Thread必须从此对象获取锁定。
  2. 第二个线程开始调用gaston.bow(alphonse)synchronized gaston对象上的Friend。这意味着Thread必须从此对象获取锁定。
  3. 现在开始的第一个线程调用bowback并等待gaston上的锁定被释放。
  4. 第二个线程现在开始调用bowback并等待alphonse上的锁定被释放。
  5. 更详细地显示事件序列:

    1. main()开始在主Therad中执行(称之为Thread#1),创建两个Friend实例。到目前为止,这么好。
    2. 主线程使用代码new Thread(new Runnable() { ...启动其第一个新线程(称为线程#2)。线程#2在alphonse.bow(gaston) synchronized对象上调用alphonseFriend。因此,线程#2获取alphonse对象的“锁定”并输入bow方法。
    3. 此处出现时间片,原始线程有机会进行更多处理。
    4. 主线程启动第二个新线程(称之为线程#3),就像第一个一样。线程#3调用gaston.bow(alphonse),它在gaston Friend对象上同步。由于没有人获得gaston对象实例的“锁定”,因此线程#3成功获取此锁并输入bow方法。
    5. 此处出现时间片,而线程#2有机会进行更多处理。
    6. 线程#2现在调用bower.bowBack(this);,其中bowergaston实例的引用。这是gaston.bowBack(alphonse)调用的逻辑等价物。因此,此方法在synchronized实例上为gaston。此对象的锁已被获取并由另一个线程(线程#3)保持。因此,线程#2必须等待gaston上的锁定被释放。线程处于等待状态,允许线程#3进一步执行。
    7. 线程#3现在调用bowback,在这种情况下,它在逻辑上与调用alphonse.bowBack(gaston)相同。为此,它需要获取alphonse实例的锁,但此锁由Thread#2保存。此线程现在处于等待状态。
    8. 现在你处于一个线程都无法执行的位置。线程#2和线程#3都在等待锁定被释放。但是没有Thread可以在没有Thread进展的情况下释放锁。但是如果没有锁被释放,两个线程都无法取得进展。

      因此:僵局!

      死锁通常取决于发生的特定事件序列,因此很难调试,因为它们很难重现。

答案 1 :(得分:2)

Synchronized has two effects

  • 首先,对同一对象的两个同步方法的调用不可能进行交错。当一个线程正在为一个对象执行一个synchronized方法时,所有其他线程都会调用同一个对象的同步方法(暂停执行),直到第一个线程完成该对象为止。
  • 其次,当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用建立一个先发生关系。这可以保证对所有线程都可以看到对象状态的更改。

简而言之,它阻止对同一对象的任何同步方法的调用。

答案 2 :(得分:2)

同一对象的所有同步函数。将方法标记为“已同步”非常类似于在该方法的整个内容周围放置“synchronized(this){”块。我不说“相同”的原因是因为我不知道编译器是否发出相同的字节码,但AFAIK定义的运行时效果是相同的。

死锁是一种经典的锁定反转。一个线程锁定alphonse。然后(或同时在多核系统上)另一个线程锁定gaston。这部分要求线程的调度恰好在正确的点处交错。

每个线程(以任何顺序或同时)然后尝试获取已由另一个线程保持的锁,因此每个线程进入休眠状态。在对方释放锁定之前,它们都不会被唤醒,但是在它被唤醒(或被终止)之前都不会释放锁定。

答案 3 :(得分:2)

synchronized方法与将所有这些方法代码封装到

中相同
synchronized(this) {
  /// code here ...
}

块。

对于给定的对象实例 o ,一次只能有一个线程运行任何 synchronized(o)块。试图嚎叫的每个其他线程,直到运行该块的线程(其上有同步锁)退出该块(放弃锁)。

在你的情况下,当Alphonse开始在线程1中弯曲时发生死锁,从而进入同步块。线程1然后被系统换出,因此线程2可以启动,并且让Gaston鞠躬。但加斯顿还不能退缩,因为它正在同步Alphonse,并且线程1已经拥有锁定。因此它将等待线程1离开该块。然后系统将交换线程1,这将尝试让阿尔方斯退回。除非它不能这样做,因为线程2在Gaston上具有同步锁定。两个线程现在都被卡住了,等待对方完成鞠躬,然后才能退后......