Подтвердить что ты не робот

Возможно ли, что нить сама зашла в тупик?

Насколько технически возможно, что поток в Java сам зашел в тупик?

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

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

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

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

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

Ответ 1

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)

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

Ответ 2

Хорошо, основываясь на определении:

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

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

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

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

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

Ответ 3

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

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

РЕДАКТ. Чтобы применить этот ответ к определению в других, конкурирующие действия здесь будут "заполнять текущий запрос" и "обрабатывать новый запрос". Каждое действие ожидает другого.

Ответ 4

Возможно, он имел в виду LOCK, что тоже слишком легко:

synchronized( this )
{
    wait( );
}

Ответ 5

Возможно, о чем думал интервьюер:

Thread.currentThread().join();

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

Тупиковая ситуация - это форма истощения ресурсов при взаимодействии нескольких потоков.

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

Примером 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)

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

Ответ 9

Пока я не использовал Java, я уже зашел в тупик приложения с одним потоком. IIRC: Routine A заблокировал часть данных для ее обновления. Routine B также заблокировал один и тот же фрагмент данных для его обновления. Из-за изменений требований A закончил вызов B. Oops.

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

Ответ 10

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

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

Deadlock detected
=================

"Negotiator-Thread-1":
  waiting to lock Monitor of c[email protected]ce4a8a
  which is held by "Kidnapper-Thread-0"

"Kidnapper-Thread-0":
  waiting to lock Monitor of co[email protected]7fc8b2
  which is held by "Negotiator-Thread-1"

Это обнаружение блокировки было доступно для встроенных замков с циклических взаимоблокировок с 1,5 и Lock с момента 1.6.

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

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

В ответ на ваш исходный вопрос, для двух танго и я предлагаю, чтобы ответ Pram не был помечен как правильный, потому что его не было!;) Нить RMI, которая перезвонит, может вызвать но он работает в другом потоке (управляемом сервером RMI), чем в главном. Два потока задействованы, даже если основной поток явно не установил другой. Отсутствует тупик, о чем свидетельствует отсутствие обнаружения в дампе потока (или если вы нажмете "обнаружение тупика" в jconsole), это будет более точно описано как livelock.

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

Ответ 11

Нет, потому что Java реализует reentrancy. Но, пожалуйста, не смешивайте concurrency и RMI. Синхронизация в заглушках - это нечто совершенно иное, чем удаленные объекты, которые внутренне синхронизированы.

Ответ 12

Вы можете попасть в один поток Deadlock с ReentrantReadWriteLock. Блокировки записи могут приобретать блокировки чтения, но не наоборот. Следующие будут блокироваться бесконечно.

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

Ответ 13

В идеале нить никогда не должна создавать тупик, используя "синхронизированные блокировки", если на самом деле не существует ошибки в самой JVM, поскольку "якобы" заметил некоторыми людьми в более старых версиях

Ответ 14

Здесь путь для самого тупика.

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

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

Ответ 15

Вы пишете поток, который может получать сообщения из других потоков, говорящих, например, о завершении. Вы пишете код в потоке, чтобы отслеживать другие потоки и отправлять им сообщения о завершении и ждать ответа. Нить найдет себя в списке, отправит себе сообщение для прекращения и ждет, когда оно закончится. Если он не был написан таким образом, чтобы приоритетные входящие сообщения переходили в состояние ожидания...

Ответ 16

Если вы растягиваете определение термина "тупик": один поток может оказаться заблокированным при не-реентерабельной блокировке, который потребовался ранее.

Ответ 17

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

Поэтому я не думаю, что это возможно.

Ответ 18

Я знаю, что это старый пост. Вот еще один пример того, как это могло произойти, если ваш код взаимодействует с внешними ресурсами:

У меня есть поток, который открывает соединение с базой данных, запускает транзакцию А и начинает обновление. Тот же поток, откройте другое соединение, запустите другую транзакцию B. Однако, поскольку транзакционная транзакция еще не зафиксирована, и она заблокировала таблицу базы данных, транзакция B имеет доступ к этой заблокированной таблице, поэтому она должна ждать

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


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

Ответ 19

Интервьюер был прав. Нить может блокировать себя в соответствии с JCIP. Но как?

В разделе 2.3.2 JCIP мы имеем следующий параграф о Reentrancy:

Reentrancy облегчает инкапсуляцию поведения блокировки и таким образом, упрощает разработку объектно-ориентированного concurrentcode. Без реэнтронтов - очень естественный код в листинге 2.7, в котором подкласс переопределяет синхронизированный метод, а затем вызывает метод суперкласса, будет deadlock.

Синхронизированная блокировка ключевых слов - это блокировка реентера, поэтому поток может блокировать и разблокировать вложенным образом, но если вы используете блокировку без реентера, как в следующем примере, я написал в качестве доказательства. У вас будет тупик! Согласно 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();
    }
}

Ответ 20

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

Вот пример в Python (я уверен, что концепция остается неизменной в Java): Если вы измените RLock на 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])