线程是否有可能死锁?
Java中的线程在技术上可能会自己死锁吗?
我在一次采访中被问到这个问题,回答说这是不可能的,但采访者告诉我说这是事实。 不幸的是我无法获得如何实现这个僵局的方法。
这让我想到了,我能想到的唯一情况就是在哪里有一个RMI服务器进程,其中包含一个调用自己的方法。 调用该方法的代码行放置在同步块中。
这甚至有可能或是面试官不正确?
我正在考虑的源代码是沿着这些线(其中testDeadlock在RMI服务器进程中运行)
public boolean testDeadlock () throws RemoteException { synchronized (this) { //Call testDeadlock via RMI loopback } }
那么,基于以下的定义:
僵局就是两个或更多的竞争行为都在等待另一个完成的情况。
我会说答案是否定的 – 确定一个线程可以无限期地等待某个东西,但是除非两个相互竞争的动作在等待对方,否则根本不是一个僵局。
除非有人向我解释单个线程如何可以同时等待两个动作完成?
更新:我可以想到的唯一可能的情况是某种消息泵,其中一个线程处理消息,要求它无限期地等待发生的事情,事实上某些东西将被消息泵上的另一个消息处理。
这个(令人难以置信的人为的)情况在技术上可能被称为僵局。
这完全取决于你所说的“僵局”。 例如,你可以在显示器上轻松地wait()
,但是我不认为我会这么叫死锁。
如果你的服务器只运行了一定数量的线程,就可以根据你自己的“调用自己的方法”思路来思考,如果重要的话,它们可能都忙着等待来自同一台服务器的响应。 (最简单的例子:服务器只使用一个线程进行处理,如果你写一个请求处理程序调用同一个服务器,它将等待被阻塞的线程完成处理请求,然后才能处理相同的请求…)这不是一个真正的“同步块”types的死锁,但它肯定是一个危险的意识到。
编辑:将这个答案应用于其他人的定义,这里的竞争行为将是“完成当前的请求”和“处理新的请求”。 每个动作都在等待另一个动作的发生。
也许他的意思是LOCK本身,这当然太简单了:
synchronized( this ) { wait( ); }
也许面试官想到的是:
Thread.currentThread().join();
不过,我认为这不算是一个僵局。
从读锁升级到写锁(尝试在持有读锁的同时获取写锁)将导致线程完全阻塞。 这是一个僵局吗? 你是裁判……但这是用单线程创build效果的最简单的方法。
http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html
根据维基百科的说法,“僵局是指两个或两个以上的竞争行为都在等待另一个完成,因此从来没有这样做。
……“在计算机科学中,科夫曼死锁是指两个或两个以上进程互相等待释放资源,或两个以上进程正在等待循环链中的资源时的特定情况。
如果你严格定义,我认为两个或两个以上是关键词。
JVM只跟踪拥有监视器的本地线程,如果调用类自己调用外部调用,则传入的调用会导致原始线程自己死锁。
你应该能够运行这个代码来说明这个想法
import java.rmi.*; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.*; public class DeadlockThreadExample { public static interface DeadlockClass extends Remote { public void execute() throws RemoteException; } public static class DeadlockClassImpl extends UnicastRemoteObject implements DeadlockClass { private Object lock = new Object(); public DeadlockClassImpl() throws RemoteException { super(); } public void execute() throws RemoteException { try { System.out.println("execute()::start"); synchronized (lock) { System.out.println("execute()::Entered Lock"); DeadlockClass deadlockClass = (DeadlockClass) Naming.lookup("rmi://localhost/DeadlockClass"); deadlockClass.execute(); } System.out.println("execute()::Exited Lock"); } catch (NotBoundException e) { System.out.println(e.getMessage()); } catch (java.net.MalformedURLException e) { System.out.println(e.getMessage()); } System.out.println("execute()::end"); } } public static void main(String[] args) throws Exception { LocateRegistry.createRegistry(Registry.REGISTRY_PORT); DeadlockClassImpl deadlockClassImpl = new DeadlockClassImpl(); Naming.rebind("DeadlockClass", deadlockClassImpl); DeadlockClass deadlockClass = (DeadlockClass) Naming.lookup("rmi://localhost/DeadlockClass"); deadlockClass.execute(); System.exit(0); } }
程序的输出如下所示
execute()::start execute()::Entered Lock execute()::start
另外线程也转储显示以下内容
"main" prio=6 tid=0x00037fb8 nid=0xb80 runnable [0x0007f000..0x0007fc3c] at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.read(SocketInputStream.java:129) at java.io.BufferedInputStream.fill(BufferedInputStream.java:218) at java.io.BufferedInputStream.read(BufferedInputStream.java:235) - locked <0x02fdc568> (a java.io.BufferedInputStream) at java.io.DataInputStream.readByte(DataInputStream.java:241) "RMI TCP Connection(4)-172.17.23.165" daemon prio=6 tid=0x0ad83d30 nid=0x1590 waiting for monitor entry [0x0b3cf000..0x0b3cfce8] at DeadlockThreadExample$DeadlockClassImpl.execute(DeadlockThreadExample.java:24) - waiting to lock <0x0300a848> (a java.lang.Object) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) "RMI TCP Connection(2)-172.17.23.165" daemon prio=6 tid=0x0ad74008 nid=0x15f0 runnable [0x0b24f000..0x0b24fbe8] at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.read(SocketInputStream.java:129) at java.io.BufferedInputStream.fill(BufferedInputStream.java:218) at java.io.BufferedInputStream.read(BufferedInputStream.java:235) - locked <0x02ffb6d8> (a java.io.BufferedInputStream) at java.io.DataInputStream.readByte(DataInputStream.java:241)
这表明线程确实设法locking自己
虽然我没有使用Java,但我之前已经死锁了一个单线程应用程序。 IIRC:例程Alocking了一段数据来更新它。 程序B也locking了相同的数据来更新它。 由于需求的变化,最后调用B. Oops。
当然,这只是我第一次尝试运行代码时遇到的一个普通的开发错误,但是它本身却造成了死锁。 我认为这种types的死锁在任何支持文件系统的语言中都是可能的。
答案(Pram)被标记为正确的,在技术上不像其他人所说的那样是一个僵局。 它刚刚被阻止。
我会build议在Java中,你可以依靠Java的定义(这是符合两个线程的想法)。 如果它自己检测到死锁,那么最终的判断可以是JVM。 所以,在Pram的例子中,如果这个线程是一个geniune的死锁,那么这个线程会显示如下的内容。
Deadlock detected ================= "Negotiator-Thread-1": waiting to lock Monitor of com.google.code.tempusfugit.concurrency.DeadlockDetectorTest$Cat@ce4a8a which is held by "Kidnapper-Thread-0" "Kidnapper-Thread-0": waiting to lock Monitor of com.google.code.tempusfugit.concurrency.DeadlockDetectorTest$Cash@7fc8b2 which is held by "Negotiator-Thread-1"
这个死锁检测已经可用于自1.5以来的内部locking以及基于Lock
的循环死锁。
一个不断被阻塞的资源,或者至less是等待永远不会发生的事情,被称为活锁(livelock) 。 类似的问题,虚拟机之外的进程(例如)数据库死锁是完全可能的,但我认为不适合这个问题。
我有兴趣写一个面试官如何声称它是可能的…
在回答你原来的问题时,需要两个探戈, 我build议Pram的答案不应被标记为正确的,因为它不是! ;)callback的RMI线程可能会导致阻塞,但它运行在不同的线程上(由RMI服务器pipe理)。 即使主线程没有明确设置另一个线程,也会涉及两个线程。 在线程转储中缺less检测(或者如果您在jconsole中单击“检测到死锁”),就没有发生死锁,它将被更准确地描述为活锁。
说了这么多的话,根据每个答案的任何讨论就足以满足我作为一个采访者。
不,因为Java实现了可重入性 。 但是请不要混淆并发性和RMI。 存根中的同步与内部同步的远程对象完全不同。
你可以让自己陷入与ReentrantReadWriteLock单线程死锁。 写锁可以获取读锁,但不能反过来。 以下将无限期阻止。
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); lock.readLock().lock(); lock.writeLock().lock();
理想情况下,一个线程不应该使用“同步锁”自己创build一个死锁,除非JVM自身存在一个bug,
这是线程自身死锁的一种方法。
public class DeadlockMe { public static void main(String[] args) { DeadlockThing foo = new DeadlockThing(); synchronized(foo) { try { foo.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
线程创build一个类的实例 – 任何类,并等待它。 因为线程创build了一个局部范围的对象,所以没有任何其他线程可以通知对象唤醒线程。
你写一个线程可以接收来自其他线程的消息告诉它,例如,终止。 你在线程中编写代码来监视其他线程并发送终止消息并等待响应。 线程会发现自己在列表中,发送一条消息终止,并等待自己终止。 如果它不是以等待状态优先接收消息的方式写入的…
如果扩展术语“死锁”的定义:单个线程可能会发现自己被locking在先前使用的非重入锁上。
当一个线程进入同步块时,它检查当前线程是否是锁的所有者,如果是,那么线程只是继续而不等待。
所以我不认为这是可能的。
我知道这是一个老post。 下面是另外一个例子,如果你的代码和外部资源有交互,它会发生什么:
我有一个线程,打开一个数据库连接,启动一个transactionA,并开始更新。 同一个线程打开另一个连接,启动另一个transactionB。 但是,因为transactionA还没有提交,并且数据库表被locking,所以transactionB碰巧访问这个locking的表,所以它必须等待
最后,同一个线程会自行阻塞,因为它打开了多个数据库连接。
在我使用的应用程序中发生了很多事情,因为应用程序中有很多模块,而且一个线程可以运行很多方法。 这些方法打开自己的连接。 由于我们有不同的开发人员编写他们的代码,他们可能不会看到他们的代码是如何开始调用的,因此看不到应用程序打开的整个数据库事务。
面试官是对的。 一个线程可以根据JCIP自己死锁 。 但是,如何?
在JCIP的2.3.2节中,我们有关于重入的以下段落:
重入便于封装locking行为,从而简化了面向对象的并发代码的开发。 如果没有reentrantlocks,代码清单2.7中很自然的代码(其中一个子类重写一个同步方法,然后调用超类方法)将会死锁。
synchronized关键字的锁是一个可重入的锁,所以一个线程可以以嵌套的方式locking和解锁,但是如果使用了一个非重入锁,就像下面的例子那样,我写了一个certificate。 你会有一个僵局! 根据JCIP。
public class SelfDeadLock { public static class Father{ volatile protected int n = 0; protected Lock ourLock = new Lock(); public void writeSth(){ try { ourLock.lock(); n++; System.out.println("Father class: " + n); } catch (InterruptedException ex) { Logger.getLogger(SelfDeadLock.class.getName()).log(Level.SEVERE, null, ex); } ourLock.unlock(); } } public static class Child extends Father{ @Override public void writeSth() { try { ourLock.lock(); n++; System.out.println("Child class: " + n); super.writeSth(); } catch (InterruptedException ex) { Logger.getLogger(SelfDeadLock.class.getName()).log(Level.SEVERE, null, ex); } ourLock.unlock(); } } public static void main(String[] args) { Child child = new Child(); child.writeSth(); } }