并发 - 为什么会出现这种僵局

时间:2014-03-26 11:12:37

标签: java multithreading concurrency

行。我在理解并发的基础知识方面遇到了一些困难。这个问题是关于死锁的。请告诉我为什么这两个线程都陷入僵局。

我从this教程中选择了这个例子。它说,

“当死锁运行时,两个线程在尝试调用bowBack时极有可能会阻塞。这两个块都不会结束,因为每个线程都在等待另一个线程退出弓箭。” < / p>

我从中理解的是:首先,他们会等待,因为当线程调用synchronized方法时,它会自动获取synchronized方法所属对象的内部锁,并继续拥有它直到方法返回;同时没有其他线程可以拥有它。

1。现在我的第一个问题是第一个线程调用zarah.bow(),因此内部锁与zarah相关联。第二个线程将调用khan.bow(),因此它将是一个不同的内部锁(因为它与一个名为khan的不同对象相关联),不是吗?

并不是zarah.bow()和khan.bow()不同?因为它们属于两个不同的实例?

2。第二个问题来自“两个”线程永远等待的概念。两个线程都将永远被阻塞,等待彼此退出。我不懂。

package Threads;

public class DeadlockModified {
    static class Friend {
        private final String name;
        Friend(String name){
            this.name=name;
        }
        private String getName(){
            return this.name;
        }
        private synchronized void bow(Friend bower){
            System.out.format("%s: %s"+" bowed to me.%n",bower.getName(),name); 
            bower.bowBack(this);
        }
        private synchronized void bowBack(Friend bower){
            System.out.format("%s: %s" + " was nice enough to bow back to me.%n",bower.getName() ,name );
        }
    }
    public static void main(String [] args){
        final Friend zarah= new Friend("Zarah");
        final Friend khan= new Friend("Khan");
        new Thread(new Runnable(){
            public void run(){zarah.bow(khan);}
        }).start();
        new Thread(new Runnable() {
            public void run(){khan.bow(zarah);}
        }).start();
    }
}

输出: -

Khan: Zarah bowed to me.
Zarah: Khan bowed to me.

提前谢谢。

修改: -

在本教程的“同步方法”一节中,写了

“从同步代码中调用其他对象的方法可能会产生”活动“部分中描述的问题。

这是关于活力的部分。我看到另一个对象的方法bowBack()是从bow()调用的。还有一些问题 - 看看程序的输出,看起来两个线程都没有执行bowBack()。但是没有给出更多细节。

3 个答案:

答案 0 :(得分:5)

  • 线程1调用zarah.bow(khan):它获取zarah引用的对象的内部锁
  • 线程2调用khan.bow(zarah):它获取khan引用的对象的内部锁
  • 线程1尝试调用khan.bowBack():它需要khan的内在锁定才能执行此操作。但是这个锁由线程2保持。因此线程1等待,直到线程1释放khan
  • 的锁
  • 线程2试图调用zarah.bowBack():它需要zarahto的内在锁定能够做到这一点。但是这个锁由线程1保持。因此线程2等待,直到线程1释放zarah
  • 的锁

两个线程因此永远等待着彼此。这是一个僵局。

答案 1 :(得分:1)

  

并不是zarah.bow()和khan.bow()不同吗?

是的,对bow()的调用是在不同的实例上执行的,但在内部他们在另一个实例上调用bowBack(),并且该调用也是同步的。

示例:zarah.bow()zarah(即调用它的实例)以及bower.bowBack(this);调用中的khan.bow(zarah)同步,因为这也可以被读取在该上下文中为zarah.bowBack(khan)bower = zarahthis = khan

  

这两个线程将永远被阻塞,等待彼此退出弓箭。

如果没有任何机制来中断其中一个线程,两个线程都会等待另一个线程离开同步部分,因为它们都在自己的同步块内等待,所以它们永远不会离开。

关于死锁的一个很好的非技术例子是dining philosophers problem。每个哲学家都会成为“哲学家”阶级的“实例”,并且在等待其他人将他们的锁放在他们的叉子上时,会在他的叉子上持有自己的内在锁。

答案 2 :(得分:1)

为了确保您真正理解这一点,请尝试使用惯用法重写方法

synchronized(Object){
enter code here
}

这将获取(Object)上的内部锁定。看看你是否可以在这个习语中复制死锁的行为。然后重写它以使其有效。

这样做会教你两件事:

  1. 调用synchronized方法会获取方法所附加对象的锁定。所以在这种情况下,zarah.bow(Khan)获得了对zarah的锁定,但没有锁定Khan。
  2. 如果您有一个调用其他同步方法的synchronized方法,则必须确保它始终获取所有锁。
  3. 在(2)上,您可以使用bow方法生成代码:

    synchronized(bower){
    System.out.format("%s: %s"+" bowed to me.%n",bower.getName(),name); 
            bower.bowBack(this);
    }
    

    这可能至少在某些时候起作用。这就形成了一种所谓的竞争条件。它会在大多数时候避免死锁,因为System.out.format是一个非常慢的执行方法,而获取锁定的速度很快。由于它缩小了获取两个锁之间的差距,因此减少了死锁的可能性,因为减少了上下文切换的机会。

    这当然是解决问题的可怕方法。这里有效的解决方案是创建一个对象,而不是调用synchronized方法,而是使用synchronized(lock)来获取该新对象的锁。然后代码将运行,因为新对象的内部锁充当代理,一次获取两个锁。创建一个新的空类:

    static class proxyLock(){};
    

    然后将方法弓重写为:

    private void bow(proxyLock lock, Friend bower){
    synchronized(lock){
        System.out.format("%s: %s"+" bowed to me.%n",bower.getName(),name); 
            bower.bowBack(this);
        }
    }
    

    main方法获取一个传递给两个线程的新代理锁,并确保只有一个线程一次运行该方法,因为另一个线程被阻塞,直到proxylock上的锁被释放,这实际上意味着该线程获得两个锁。

    当然,这个成语有它的问题,没有什么可以阻止别人写一个不知道proxylock并设置死锁的方法,这就是为什么你应该总是记录你的同意协议。任何为并发而设计的类都应该有一个java doc条目来解释它如何实现线程安全性。

    另外,在实践中阅读并发性。它是Java Concurrency迄今为止最好的书。