一个线程可能会死锁吗?

时间:2010-08-16 13:14:04

标签: java multithreading deadlock rmi

Java中的线程技术上是否可能使其自身死锁?

我在接受采访时被问到这个问题并回复说这是不可能的,但面试官告诉我这是。不幸的是,我无法获得如何实现这种僵局的方法。

这让我思考,我能想到的唯一情况就是你可以实现这一点的地方就是你有一个RMI服务器进程,其中包含一个调用自身的方法。调用该方法的代码行放在同步块中。

这是可能的还是面试官不正确?

我正在考虑的源代码是这些行(其中testDeadlock在RMI服务器进程中运行)

public boolean testDeadlock () throws RemoteException {
    synchronized (this) {
        //Call testDeadlock via RMI loopback            
    }
}

20 个答案:

答案 0 :(得分:50)

嗯,基于以下定义:

  

死锁是指两个或多个竞争行为各自等待另一个完成的情况。

我会说答案是肯定的 - 确定线程可以无限期地等待某些东西,但是除非两个竞争行为正在等待彼此,否则它根本不是死锁。

除非有人向我解释一个线程如何同时等待两个动作完成?

更新:我能想到的唯一可能的情况是某种消息泵,其中一个线程处理一条消息,要求它无限期地等待某事事实上,消息泵上的另一条消息将处理某事

这种(令人难以置信的设计)场景在技术上可能被称为死锁。

答案 1 :(得分:18)

这完全取决于“死锁”的含义。例如,您可以轻松wait()在监视器上没有任何东西可以脉冲...但我不认为我会称之为死锁。

如果您的服务器只运行一定数量的线程,那么根据您的“调用自身的方法”行,他们可能会忙着等待来自同一服务器的响应,如果这很重要的话。 (最简单的例子:服务器只使用一个线程进行处理。如果你编写一个调用同一服务器的请求处理程序,它将等待被阻塞的线程完成处理请求,然后才能提供相同的请求......)这实际上并不是一种“同步阻塞”的死锁,但这一点肯定是一种危险。

编辑:要将此答案应用于其他人的定义,此处的竞争操作将是“完成当前请求”和“处理新请求”。每个动作都在等待另一个动作发生。

答案 2 :(得分:10)

也许他的意思是 LOCK 本身,这当然太容易了:

synchronized( this )
{
    wait( );
}

答案 3 :(得分:7)

面试官的想法可能是:

Thread.currentThread().join();

但是我认为它不算是一个僵局。

答案 4 :(得分:6)

从读锁升级到写锁(尝试在保持读锁时尝试获取写锁)将导致线程完全被阻塞。这是一个僵局吗?你是判断者...但这是用单个线程创建效果的最简单方法。

http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html

答案 5 :(得分:6)

死锁是资源匮乏的一种形式,它在多个线程之间进行交互。

当线程进入资源保存状态时,它被称为livelock,它类似于死锁,但根据定义不同。

一个活锁的例子是使用ReentrantReadWriteLock。尽管在读或写方面有所折衷,但不允许将锁从读升级为写。

public class LiveLock {
    public static void main(String[] args) {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        lock.readLock().lock();
        if (someCondition()) {
            // we want to write without allowing another thread to jump in.
            lock.writeLock().lock();
        }
    }

    private static boolean someCondition() {
        return true;
    }
}

导致此处阻止的过程

"main" #1 prio=5 os_prio=0 tid=0x0000000002a52800 nid=0x550c waiting on condition [0x000000000291f000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000007162e5e40> (a java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
    at java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock.lock(ReentrantReadWriteLock.java:943)
    at LiveLock.main(LiveLock.java:10)

一个相关的问题是;线程可以陷入死锁而无需创建其他线程。这是可能的,因为有后台线程,例如终结器线程,该线程可以在后台运行用户代码。这允许主线程和终结器线程彼此死锁。

答案 6 :(得分:5)

根据维基百科的说法,“僵局是一种情况,其中两个或更多的竞争行动都在等待另一个完成,因此两者都没有。”

...“在计算机科学中,Coffman死锁指的是当两个或多个进程各自等待释放资源,或者两个以上的进程在循环链中等待资源时的特定条件。”< / p>

如果您对定义保持严格,我认为两个或更多是关键词。

答案 7 :(得分:4)

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)

表示该线程确实设法锁定了自己

答案 8 :(得分:3)

标记为正确的答案(Pram)在技术上并不像其他人所暗示的那样陷入僵局。它只是被封锁了。

我建议在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.6以来基于1.5和Lock的循环死锁,此死锁检测已用于内部锁定。

持续阻止的资源,或者至少等待永远不会发生的事情被称为活锁。类似的问题,VM(例如)数据库死锁之外的进程完全可能,但我认为不适合这个问题。

我有兴趣写一篇关于采访者声称可能的内容......

在回答你的原始问题时,需要两个探戈和我建议Pram的答案不应该被标记为正确,因为它不是!;)回调的RMI线程可能导致阻塞但它运行在一个不同的线程(由RMI服务器管理)而不是main。即使主线程未明确设置另一个线程,也会涉及两个线程。由于线程转储中缺少检测(或者如果你点击jconsole中的'detect deadlock'),没有死锁,所以它更准确地描述为活锁。

说了这么多之后,任何与这些答案相符的讨论都足以让我满足于作为面试官。

答案 9 :(得分:2)

虽然我没有使用过Java,但我之前已经死了一个单线程应用程序。 IIRC:Routine A锁定一条数据来更新它。例程B也锁定了同一条数据来更新它。由于要求的变化,A最终打电话给B.糟糕。

当然这只是我第一次尝试运行代码时遇到的一个普通的开发错误,但它本身已经死锁。我认为在任何支持文件系统的语言中都可以使用这种类型的死锁。

答案 10 :(得分:1)

不,因为Java实现了重入。但请不要混淆并发和RMI。存根中的同步与内部同步的远程对象完全不同。

答案 11 :(得分:1)

你可以通过ReentrantReadWriteLock将自己置于单一线程死锁中。写锁可以获取读锁,但不是相反。以下将无限期阻止。

    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    lock.readLock().lock();
    lock.writeLock().lock();

答案 12 :(得分:0)

尽管如果至少有两个线程/动作在争用同一资源,那么这里的评论是在“死锁”的发生上令人讨厌,我认为这个问题的实质是讨论对可重入锁的需求-尤其是在上下文中递归锁定

这是python中的一个示例(我敢肯定Java中的概念保持不变): 如果您将RLock更改为Lock(即,将可重入锁更改为Lock,则线程将挂起)

import threading

"""
Change RLock to Lock to make it "hang"
"""
lock = threading.Condition(threading.RLock())


def print_list(list):
    lock.acquire()
    if not list:
        lock.release()
        return
    print(list[0])
    print_list(list[1:])
    lock.release()


print_list([1, 2, 3, 4, 5])

答案 13 :(得分:0)

面试官是对的。 根据JCIP ,线程可以自行解锁。但是如何?

在JCIP第2.3.2节中,我们有关于可重入性的以下段落:

  

Reentrancy有助于封装锁定行为,并且   从而简化了面向对象的开发   concurrentcode。没有reentrantlocks,代码清单2.7中非常自然的代码,其中一个子类重写了一个   synchronized方法然后调用超类方法,会死锁。

synchronized关键字的锁是一个可重入锁,因此线程可以嵌套方式锁定和解锁,但如果您使用非重入锁,如下面的示例,我写的是证明。你会陷入僵局!根据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();
    }
}

答案 14 :(得分:0)

我知道这是一个老帖子。下面是另一个示例,如果您的代码与外部资源进行交互,它将如何发生:

我有一个线程,打开数据库连接,启动事务A,然后开始更新。同一个线程,打开另一个连接,启动另一个transactionB。但是,由于transactionA尚未提交,并且数据库表已锁定,因此事务B恰好访问此锁定表,因此必须等待

最后,同一个线程本身就是块,因为它打开了多个数据库连接。


在我使用的应用程序中发生了很多事情,因为应用程序中有许多模块,并且线程可以通过许多方法运行。这些方法打开了自己的联系。由于我们有不同的开发人员编写他们的代码,他们可能看不到他们的代码是如何开始调用的,因此无法看到应用程序打开的整个数据库事务。

答案 15 :(得分:0)

当线程进入同步块时,它会检查当前线程是否是锁的所有者,如果是,则线程只是继续而不等待。

所以我认为不可能。

答案 16 :(得分:0)

如果你延长术语死锁的定义:单个线程可以发现自己在之前采用的非重入锁上被阻塞。

答案 17 :(得分:0)

你编写一个线程,可以从其他线程接收消息,告诉它,例如,终止。您在线程中编写代码来监视其他线程并发送它们终止消息并等待响应。线程会在列表中找到自己,向自己发送一条消息以终止并等待自己终止。如果它不是以等待状态优先传入消息的方式编写的......

答案 18 :(得分:0)

这是线程自行死锁的一种方式。

public class DeadlockMe
{
    public static void main(String[] args)
    {
        DeadlockThing foo = new DeadlockThing();
        synchronized(foo)
        {
            try
            {
                foo.wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
}

线程创建一个类的实例 - 任何类并等待它。因为线程创建了一个具有本地范围的对象,所以任何其他线程都无法通知对象唤醒线程。

答案 19 :(得分:0)

理想情况下,一个线程永远不应该使用'synchronized locks'自己创建一个死锁,除非JVM本身确实存在一个错误,因为旧版本中的某些人有'allegedly' noticed