Is it possible for a thread to Deadlock itself?

2020-01-29 03:20发布

Is it technically possible for a thread in Java to deadlock itself?

I was asked this at an interview a while back and responded that it wasn't possible but the interviewer told me that it is. Unfortunately I wasn't able to get his method on how to achieve this deadlock.

This got me thinking and the only situation that I can think of is where you can have this happen is where you have an RMI server process which contained a method that calls itself. The line of code that calls the method is placed in a synchronized block.

Is that even possible or was the interviewer incorrect?

The source code I was thinking about was along these lines (where testDeadlock is running in an RMI server process)

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

20条回答
聊天终结者
2楼-- · 2020-01-29 03:57

Maybe what the interviewer was thinking of was:

Thread.currentThread().join();

However I would argue that it does not count as a deadlock.

查看更多
聊天终结者
3楼-- · 2020-01-29 03:58

Ideally a thread should never create a deadlock itself using 'synchronized locks' unless there really is a bug in the JVM itself as 'allegedly' noticed by some people in older versions

查看更多
欢心
4楼-- · 2020-01-29 03:59

Maybe he meant LOCK itself, that's certainly too easy:

synchronized( this )
{
    wait( );
}
查看更多
We Are One
5楼-- · 2020-01-29 03:59

The JVM only keeps track of the local thread that has the monitor, if the calling class makes an external call back in on itself the incoming call causes the original thread to deadlock itself.

You should be able to run this code to illustrate the idea

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);
    }
}

The output from the program looks like

execute()::start
execute()::Entered Lock
execute()::start

Additionally the thread also dump shows the following

"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)

which indicates that the thread has indeed managed to lock itself

查看更多
Luminary・发光体
6楼-- · 2020-01-29 03:59

The interviewer was right. A thread can deadlock itself according to JCIP. But how?

In the section 2.3.2 of the JCIP we have the following paragraph about Reentrancy:

Reentrancy facilitates encapsulation of locking behavior, and thus simplifies the development of objectͲoriented concurrentcode. Without reentrantlocks, the very natural-looking code in Listing 2.7, in which a subclass overrides a synchronized method and then calls the super class method, would deadlock.

The synchronized keyword's lock is a reentrant lock so a thread can lock and unlock in a nested manner but if you use a non-reentrant lock like the following example I wrote as a proof. You will have a deadlock! According to 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();
    }
}
查看更多
虎瘦雄心在
7楼-- · 2020-01-29 04:00

The answer (Pram's) marked as correct isn't technically a deadlock as others have suggested. Its just blocked.

I would suggest in Java, you can lean on Java's definition (which is consistent with the two thread idea). The ultimate judge then can be the JVM if it detects the deadlock itself. So, in Pram's example, the thread would show something like the following if it was a geniune deadlock.

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"

This deadlock detection has been available for intrinsic locks since 1.5 and Lock based cyclic deadlocks since 1.6.

A continuously blocked resource, or at least something that is waiting for something that will never happen is called a livelock. Similar problems where processes outside the VM (for example) databases deadlocking are entirely possible but I'd argue not appropriate for the question.

I'd be interested in a write up of how the interviewer claims its possible...

In answer to your original question, it takes two to tango and I'd suggest Pram's answer shouldn't be marked as correct because its not! ;) The RMI thread which calls back can cause blocking but it runs on a different thread (managed by the RMI server) than that of main. Two threads are involved even if the main thread didn't explicitly set another one up. There is no deadlock as witnessed by the lack of the detection in the thread dump (or if you click 'detect deadlock' in jconsole), it'd be more accurately described as a livelock.

Having said all that, any discussion along the lines of each of these answers would be enough to satisfy me as an interviewer.

查看更多
登录 后发表回答