使用System.out.format和System.out.println进行multithreading处理
我在Oracle的Java Tutorial中遇到了这个例子 ,描述了multithreading场景中的死锁。
所以在这个例子中,我在第17行和第18行进行了以下更改。
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) { //My Changes //System.out.format("%s: %s" + " has bowed to me!%n", this.name, bower.getName()); //Line 17 System.out.println(this.name + ": " + bower.getName() + " has bowed to me!"); //Line 18 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() { @Override public void run() { alphonse.bow(gaston); } }).start(); new Thread(new Runnable() { @Override public void run() { gaston.bow(alphonse); } }).start(); } }
执行这些更改后,程序成功终止,不会导致死锁并打印输出
Alphonse: Gaston has bowed to me! Gaston: Alphonse has bowed back to me! Gaston: Alphonse has bowed to me! Alphonse: Gaston has bowed back to me!
所以我的问题是 – 为什么它表现得像这样? println语句是如何防止死锁的?
您是否使用System.out.print
或System.out.format
没有区别:它们基本上做同样的事情。
如果在Alphonse.bow(Gaston)
和bower.bowBack(Alphonse)
开始之间开始执行Gaston.bow(Alphonse)
,则会发生死锁:反之亦然:两个线程正在等待另一个持有的监视器,从而发生死锁。
这种情况不一致,因为它取决于线程如何安排的细微计时问题 – 在Gaston.bow
执行之前Alphonse.bow
和bower.backBack(Alphonse)
完成,所以看起来没有僵局。
解决此问题的经典方法是订购锁定,以便每次首先获取相同的锁定; 这可以防止死锁的可能性:
public void bow(Friend bower) { // Method no longer synchronized. int firstHash = System.identityHashCode(this); int secondHash = System.identityHashCode(bower); Object firstMonitor = firstHash < secondHash ? this : bower; Object secondMonitor = firstHash < secondHash ? bower : this; synchronized (firstMonitor) { synchronized (secondMonitor) { // Code free (*) of deadlocks, with respect to this and bower at least. } } }
(*)它不能完全保证死锁,因为System.identityHashCode
可以为不同的对象返回相同的值; 但这是不太可能的。
这是生日悖论的应用:如果你只有两台显示器,那么碰撞的可能性就像10 ^ -18; 但是如果你有超过77k的显示器,那么碰撞就更有可能了。
你在这里混淆东西。
一段代码可能导致死锁情况这一事实并不一定意味着您每次以及代码运行时都会收到死锁。
这是使multithreading成为一个如此难题的方面之一:如果你运行一次代码,或者10次,或者100次,并且一切都“有效”; 它仍有可能在下次失败。
换句话说:尝试将该代码放在最外层的循环中,迟早(可能更早;如果你没有做太多“睡觉”),你应该遇到僵局!
如果事情变得如此简单并且可以轻松地检测到死锁,我们就不需要所有这些书籍和库以及如何处理multithreading的想法……
为了支持其余的答案,我提供了一些实际的证据,我在循环中运行了你的代码,并且在100次尝试中死了82次,所以你的代码肯定仍然是死锁。
死锁完全不依赖于println函数。 它是由两个线程试图互相访问并相互锁定引起的。
从格式到println的更改将在程序中引入足够的延迟,以允许线程彼此锁定而不会发生冲突,即死锁。 所以你还没有真正解决它; 你刚刚添加了一些延迟,这意味着线程不会死锁。