行。我在理解并发的基础知识方面遇到了一些困难。这个问题是关于死锁的。请告诉我为什么这两个线程都陷入僵局。
我从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()。但是没有给出更多细节。
答案 0 :(得分:5)
zarah.bow(khan)
:它获取zarah
引用的对象的内部锁khan.bow(zarah)
:它获取khan
引用的对象的内部锁khan.bowBack()
:它需要khan的内在锁定才能执行此操作。但是这个锁由线程2保持。因此线程1等待,直到线程1释放khan
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 = zarah
和this = khan
。
这两个线程将永远被阻塞,等待彼此退出弓箭。
如果没有任何机制来中断其中一个线程,两个线程都会等待另一个线程离开同步部分,因为它们都在自己的同步块内等待,所以它们永远不会离开。
关于死锁的一个很好的非技术例子是dining philosophers problem。每个哲学家都会成为“哲学家”阶级的“实例”,并且在等待其他人将他们的锁放在他们的叉子上时,会在他的叉子上持有自己的内在锁。
答案 2 :(得分:1)
为了确保您真正理解这一点,请尝试使用惯用法重写方法
synchronized(Object){
enter code here
}
这将获取(Object)上的内部锁定。看看你是否可以在这个习语中复制死锁的行为。然后重写它以使其有效。
这样做会教你两件事:
在(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迄今为止最好的书。