关于Java中死锁情况的问题
我正在学习Java中的死锁,并且有来自Sun官方教程的示例代码:
阿方斯和加斯顿是朋友,也很有礼貌的信徒。 严格的礼貌规则是,当你向朋友鞠躬时,你必须保持鞠躬,直到你的朋友有机会归还弓。 不幸的是,这条规则没有考虑到两个朋友可能同时互相鞠躬的可能性。
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(); } }
这是Sun的解释:
当Deadlock运行时,两个线程在尝试调用bowBack时极有可能会阻塞。 两个块都不会结束,因为每个线程都在等待另一个线程退出弓。
我似乎不太关注。 当alphonse.bow(gaston)运行时,弓法被锁定。 所以现在它首先打印“加斯顿向我鞠躬!”。 然后它会继续并调用bowBack,并锁定bowBack。 这怎么会导致僵局? 我是否误解了调用同步方法时会发生什么?
如果有人能给我一个简单的解释,谢谢。
需要注意的一点是,它不是被锁定的方法 ,而是对象实例 。
当你打电话给alphonse.bow(gaston)
,它试图获得对alphonse
的锁定。 一旦它有锁,它会打印一条消息,然后调用gaston.bowBack(alphonse)
。 此时,它试图获取gaston
的锁定。 一旦锁定,它就会打印一条消息,然后释放锁定,最后释放对alphonse
的锁定。
在死锁中,以这样的顺序获取锁,使得任一线程都无法继续。
- 线程1:获得对
alphonse
锁定 - 线程2:获取
gaston
锁定 - 线程1:打印消息
- 线程1:尝试获取
gaston
上的gaston
– 不能,因为线程2已经拥有它。 - 线程2:打印消息
- 线程2:尝试获取对
alphonse
锁定 – 不能,因为线程1已经拥有它。
阿方斯和加斯顿是两个不同的对象。 每个对象都有一个与之关联的内在监视器(锁)。
可能会发生这样的事情:
alphonse已创建。 他的对象监视器是1。
gaston已创建。 他的对象监视器是2。
alphonse.bow(Gaston)的; 阿尔方斯现在拥有锁#1
gaston.bow(阿方); 加斯顿现在拥有锁#2
alphonse在gaston上调用bowBack并正在等待锁#2 gaston在alphonse上调用bowBack并等待锁定#1
合理? 使用实例监视方法持续时间的synchronized关键字锁。 该示例可以重写如下:
public class Deadlock { static class Friend { private final String name; public Friend(String name) { this.name = name; } public String getName() { return this.name; } public void bow(Friend bower) { synchronized(this) { System.out.format("%s: %s has bowed to me!%n", this.name, bower.getName()); bower.bowBack(this); } } public void bowBack(Friend bower) { synchronized(this) { System.out.format("%s: %s has bowed back to me!%n", this.name, bower.getName()); } } } }
锁定在Java对象上,而不是Java方法上。 因此,当在方法上使用synchronized时,它会锁定“this”对象。 在静态方法的情况下,它锁定类对象。
您可以使用synchronized ( object ) { }
显式指定监视器对象
添加到simonn等人,
如果您想要将其可视化,请编译并运行该程序并生成正在运行的程序的线程转储。 您可以通过在Windows控制台上键入Ctrl – Break或向* nix系统发出kill -QUIT [pid]
命令来执行此操作。 这将为您提供一个列表,列出系统中运行的所有Threads
以及它们正在运行或等待的位置,以及线程锁定或等待锁定的监视器。
如果在其构造函数中更改了线程名称,则可以更容易地在完整的线程转储中找到它们:
new Thread(new Runnable() { public void run() { alphonse.bow(gaston); } }, "Alphonse").start(); new Thread(new Runnable() { public void run() { gaston.bow(alphonse); } }, "Gaston").start();
方法定义中的synchronized是同步对象(this)本身的简写。 实质上,这意味着不能在相同的Friend对象上相互调用bow()和bowBack()。
现在,如果两个朋友都进入bow(),他们都不能调用彼此的bowBack()方法。
我必须仔细检查,但我认为一个synchronized方法锁定了类对象,因此它锁定了同一个类中的其他同步方法。
我认为它会锁定类对象本身,因此它甚至会阻止不同的实例。
编辑添加:
看一下java语言规范的这一部分
每个弓法都抓住它自己的物体监视器。 然后两者都试图将另一个对象的弓向后调用,并阻止等待另一个监视器。