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


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

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

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

Это вообще возможно или интервьюер неправильно?

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

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

18 ответов:

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

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

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

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

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

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

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

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

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

может быть, он имел в виду замок сам по себе, это, конечно, слишком легко:

synchronized( this )
{
    wait( );
}

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

Thread.currentThread().join();

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

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

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)

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

хотя я не использовал Java, я заблокировал приложение с одним потоком раньше. IIRC: процедура a заблокировала часть данных для ее обновления. Подпрограмма B также заблокировала один и тот же фрагмент данных для его обновления. В связи с изменениями требований в итоге звоню В. Упс.

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

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

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

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 (например) базы данных взаимоблокировки вполне возможны, но я бы сказал, что не подходит для этого вопроса.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

В разделе 2.3.2 для JCIP мы имеем следующие пункте о Реентерабельности:

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

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