Возможно ли для нити сам Deadlock? - PullRequest
56 голосов
/ 16 августа 2010

Технически возможно, чтобы поток в Java самоблокировался?

Меня спросили об этом на собеседовании некоторое время назад, и он ответил, что это невозможно, но интервьюер сказал мне, что это так.К сожалению, я не смог получить его метод о том, как добиться этого тупика.

Это заставило меня задуматься, и единственная ситуация, о которой я могу подумать, это то, где вы можете сделать это, когда у вас есть сервер RMI.Процесс, который содержит метод, который вызывает себя.Строка кода, вызывающая метод, помещается в синхронизированный блок.

Возможно ли это или же интервьюер неверен?

Исходный код, о котором я думал, был по этим направлениям (гдеtestDeadlock выполняется в процессе сервера RMI)

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

Ответы [ 20 ]

51 голосов
/ 16 августа 2010

Ну, исходя из определения:

Тупик - это ситуация, когда два или более конкурирующих действия ждут завершения другого.

Я бы сказал, что ответ - нет, конечно, поток может сидеть и ждать чего-то бесконечно, однако, если два конкурирующих действия не ждут друг друга, то это по определению не тупик.

Если кто-то не объяснит мне, как один поток может одновременно ожидать завершения двух действий?

ОБНОВЛЕНИЕ: Единственная возможная ситуация, о которой я могу думать, - это своего рода насос сообщений, где поток обрабатывает сообщение, которое просит его ждать неопределенно что-то , где на самом деле что-то будет обработано другим сообщением в насосе сообщений.

Этот (невероятно надуманный) сценарий можно технически назвать тупиком.

18 голосов
/ 16 августа 2010

Это зависит от того, что именно вы подразумеваете под «тупиком».Например, вы могли бы легко wait() на мониторе, который никогда не будет пульсировать ... но я не думаю, что я бы назвал этот тупик, как таковой.

Думая по-своему "метод, который вызывает сам"Линии, если на вашем сервере запущено только определенное количество потоков, все они могут быть заняты ожиданием ответов от того же сервера, если это имеет значение.(Простейший пример: сервер использует только один поток для обработки. Если вы напишите обработчик запроса, который обращается к тому же серверу, он будет ждать, пока заблокированный поток завершит обработку запроса, прежде чем он сможет обработать тот же запрос ...)На самом деле это не тупик типа «синхронизированного блока», но, безусловно, опасно осознавать.

РЕДАКТИРОВАТЬ: чтобы применить этот ответ к определению в других, конкурирующие действия здесь будут "завершить текущий запрос »и« обработать новый запрос ».Каждое действие ожидает, когда произойдет другое.

10 голосов
/ 16 августа 2010

Может быть, он имел в виду LOCK , это, конечно, слишком просто:

synchronized( this )
{
    wait( );
}
7 голосов
/ 16 августа 2010

Может быть, то, о чем думал интервьюер:

Thread.currentThread().join();

Однако я бы сказал, что это не считается тупиком.

6 голосов
/ 05 ноября 2018

Дедлок является формой нехватки ресурсов с взаимодействием между несколькими потоками.

Когда поток переходит в состояние сохранения ресурса, он называется 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 голосов
/ 16 августа 2010

Обновление с блокировки чтения до блокировки записи (попытка получить блокировку записи при удержании блокировки чтения) приведет к тому, что поток будет полностью заблокирован.Это тупик?Вы будете судьей ... Но это самый простой способ создать эффект с помощью одной нити.

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

5 голосов
/ 16 августа 2010

Согласно Википедии, «тупик - это ситуация, когда два или более конкурирующих действия ждут завершения другого, и поэтому ни один из них не делает этого».

... "В области компьютерных наук Коффман«взаимоблокировка» относится к определенному условию, когда два или более процессов каждый ожидают друг друга, чтобы освободить ресурс, или более двух процессов ожидают ресурсы в круговой цепочке. "

Я думаю два илидалее являются ключевыми словами, если вы строго придерживаетесь определения.

4 голосов
/ 08 сентября 2010

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)

, который указывает, что потоку действительно удалось заблокировать себя

3 голосов
/ 31 декабря 2010

Ответ (Pram's), помеченный как правильный, технически не является тупиковым, как предлагали другие. Его только что заблокировали.

Я бы сказал, что в 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 и на основе циклических блокировок Lock начиная с 1.6.

Постоянно блокируемый ресурс или, по крайней мере, то, что ожидает чего-то, что никогда не произойдет, называется livelock . Подобные проблемы, когда процессы вне взаимных блокировок баз данных VM (например) вполне возможны, но я бы сказал, что не подходит для этого вопроса.

Мне было бы интересно написать, как интервьюер утверждает, что это возможно ...

В ответ на ваш первоначальный вопрос, для танго нужны двое, и Я бы предложил, чтобы ответ Прам не был помечен как правильный, потому что это не так! ;) Поток RMI, который вызывает обратно, может вызвать блокировку но он работает в другом потоке (управляемом сервером RMI), чем основной. Два потока вовлечены, даже если основной поток явно не настроил другой. Отсутствует взаимоблокировка, о чем свидетельствует отсутствие обнаружения в дампе потока (или, если вы нажмете «обнаружить взаимоблокировку» в jconsole), это будет более точно описано как «живая» блокировка.

Сказав все это, любой дискуссии по каждому из этих ответов будет достаточно, чтобы удовлетворить меня как интервьюера.

2 голосов
/ 16 августа 2010

Хотя я не использовал Java, я ранее блокировал однопоточное приложение. IIRC: Рутина А заблокировала часть данных для ее обновления. Подпрограмма B также заблокировала ту же часть данных, чтобы обновить ее. Из-за изменений требований А в итоге позвонил Б. Упс.

Конечно, это была обычная ошибка разработки, которую я обнаружил в первый раз, когда попытался запустить код, но он заблокировал сам себя. Я думаю, что блокировки такого типа возможны на любом языке, который поддерживает файловую систему.

...