IT TIP

스레드가 자체적으로 교착 상태가 될 수 있습니까?

itqueen 2020. 12. 29. 08:10
반응형

스레드가 자체적으로 교착 상태가 될 수 있습니까?


Java의 스레드가 자체적으로 교착 상태가되는 것이 기술적으로 가능합니까?

얼마 전 인터뷰에서이 질문을 받았고 불가능하다고 대답했지만 면접관이 그렇다고 말했습니다. 불행히도 나는이 교착 상태를 달성하는 방법에 대한 그의 방법을 얻을 수 없었습니다.

이것이 저를 생각하게했고 제가 생각할 수있는 유일한 상황은 자신을 호출하는 메서드를 포함하는 RMI 서버 프로세스가있는 경우입니다. 메서드를 호출하는 코드 줄은 동기화 된 블록에 배치됩니다.

그것이 가능하거나 면접관이 틀렸습니까?

내가 생각했던 소스 코드는이 라인을 따라 있었다 (testDeadlock이 RMI 서버 프로세스에서 실행되는 곳)

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

글쎄요, 정의에 따르면 :

교착 상태는 둘 이상의 경쟁 작업이 각각 다른 작업이 완료되기를 기다리는 상황입니다.

대답은 '아니오'라고 말하고 싶습니다. 스레드가 무언가를 위해 무기한 대기 할 수 있다는 것이 확실하지만, 두 개의 경쟁 동작이 서로를 기다리고 있지 않는 한 그것은 정의상 교착 상태가 아닙니다.

누군가가 단일 스레드가 두 작업이 완료되기를 동시에 기다리는 방법을 설명하지 않는 한?

업데이트 : 내가 생각할 수있는 유일한 상황은 일종의 메시지 펌프입니다. 스레드가 어떤 일이 발생할 때까지 무기한 대기하도록 요청하는 메시지를 처리합니다 . 사실 메시지 펌프의 다른 메시지에 의해 무언가 가 처리 될 입니다. .

이 (놀랍게도 인위적인) 시나리오는 기술적으로 교착 상태라고 할 수 있습니다.


정확히 "교착 상태"가 의미하는 바에 따라 다릅니다. 예를 들어, wait()아무것도 펄럭이지 않는 모니터에서 쉽게 할 수 있습니다 .하지만 저는 그 교착 상태라고 부르지 않을 것입니다.

"자신을 호출하는 방법"행을 고려할 때 서버가 특정 수의 스레드 만 실행했다면 모두 동일한 서버의 응답을 기다리느라 바쁠 수 있습니다. (가장 간단한 예 : 서버는 처리를 위해 하나의 스레드 만 사용합니다. 동일한 서버를 호출하는 요청 핸들러를 작성하면 차단 된 스레드가 동일한 요청을 처리하기 전에 요청 처리를 완료 할 때까지 기다립니다 ...) 이것은 실제로 "동기화 된 블록"종류의 교착 상태는 아니지만 확실히 알아 두어야 할 위험입니다.

편집 :이 대답을 다른 정의에 적용하려면 여기에서 경쟁하는 작업은 "현재 요청 완료"및 "새 요청 처리"입니다. 각 작업은 다른 작업이 발생하기를 기다리고 있습니다.


아마도 그는 LOCK 자체 를 의미했을 것입니다. 그것은 확실히 너무 쉽습니다.

synchronized( this )
{
    wait( );
}

면접관이 생각한 것은 다음과 같을 것입니다.

Thread.currentThread().join();

그러나 나는 그것이 교착 상태로 간주되지 않는다고 주장합니다.


읽기 잠금에서 쓰기 잠금으로 업그레이드하면 (읽기 잠금을 유지하면서 쓰기 잠금을 얻으려고 시도) 스레드가 완전히 차단됩니다. 교착 상태입니까? 당신이 판사입니다 ...하지만 이것이 단일 스레드로 효과를 만드는 가장 쉬운 방법입니다.

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


교착 상태는 여러 스레드 간의 상호 작용으로 인한 리소스 고갈의 한 형태입니다.

스레드가 리소스를 훔치는 상태가되면 교착 상태와 비슷하지만 정의상 동일하지 않은 라이브 록참조합니다 .

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)

관련 질문은 다음과 같습니다. 추가 스레드를 만들지 않고도 스레드가 교착 상태에 빠질 수 있습니다. 이는 백그라운드에서 사용자 코드를 실행할 수있는 종료 자 스레드와 같은 백그라운드 스레드가 있기 때문에 가능합니다. 이렇게하면 주 스레드와 종료 자 스레드가 서로 교착 상태가됩니다.


Wikipedia에 따르면, "교착 상태는 두 개 이상의 경쟁 작업이 각각 다른 작업이 완료되기를 기다리고 있으므로 어느 쪽도 수행하지 않는 상황입니다."

... "컴퓨터 과학에서 Coffman 교착 상태는 두 개 이상의 프로세스가 서로 리소스를 해제하기 위해 서로 기다리고 있거나 두 개 이상의 프로세스가 순환 체인에서 리소스를 기다리고있는 특정 조건을 의미합니다."

정의를 엄격하게 유지한다면 여기서 두 개 이상의 키워드가 키워드 라고 생각 합니다.


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)

이는 스레드가 실제로 자체적으로 잠 겼음을 나타냅니다.


올바른 것으로 표시된 답변 (Pram 's)은 ​​다른 사람들이 제안한 것처럼 기술적으로 교착 상태가 아닙니다. 막 막혔어요.

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 이후의 고유 잠금 및 Lock1.6 이후의 기반 순환 교착 상태 에 사용할 수 있습니다 .

지속적으로 차단 된 리소스 또는 적어도 발생하지 않을 일을 기다리는 것을 라이브 락 이라고합니다 . VM 외부의 프로세스 (예 : 데이터베이스 교착 상태)와 유사한 문제가 전적으로 가능하지만 질문에 적합하지 않다고 생각합니다.

면접관이 가능하다고 주장하는 방법에 대한 글에 관심이 있습니다.

원래 질문에 대한 대답으로 탱고에 두 번 걸리며 유모차의 대답은 정확하지 않기 때문에 표시되어서는 안됩니다! ;) 콜백하는 RMI 스레드는 블로킹을 유발할 수 있지만 main 스레드와 다른 스레드 (RMI 서버에서 관리)에서 실행됩니다. 메인 스레드가 명시 적으로 다른 스레드를 설정하지 않았더라도 두 스레드가 관련됩니다. 스레드 덤프에서 감지가 부족한 것으로 확인되는 교착 상태가 없습니다 (또는 jconsole에서 '교착 상태 감지'를 클릭하면). 더 정확하게는 라이브 락으로 설명됩니다.

모든 것을 말했듯이, 이러한 각 답변의 라인을 따라 논의하는 것은 면접관으로서 나를 만족시키기에 충분할 것입니다.


Java를 사용하지 않았지만 이전에 단일 스레드 앱을 교착 상태로 만들었습니다. IIRC : Routine A가 데이터를 업데이트하기 위해 잠겼습니다. 루틴 B도 동일한 데이터를 잠그고 업데이트했습니다. 요구 사항 변경으로 인해 A가 B에게 전화를 걸었습니다. 죄송합니다.

물론 이것은 내가 처음으로 코드를 실행하려고 시도했을 때 잡은 일반적인 개발 버그 일뿐 교착 상태가되었습니다. 이 유형의 교착 상태는 파일 시스템을 지원하는 모든 언어에서 가능할 것이라고 생각합니다.


아니요, Java는 재진입을 구현하기 때문 입니다. 그러나 이와 같이 동시성과 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();
            }
        }
    }
}

스레드는 모든 클래스의 인스턴스를 만들고 기다립니다. 스레드가 로컬 범위로 개체를 만들었 기 때문에 다른 스레드가 개체에 스레드를 깨우도록 알릴 수있는 방법이 없습니다.


You write a thread which can receive messages from other threads telling it to, for example, terminate. You write code in the thread to monitor other threads and send them terminate messages and wait for a response. The thread would find itself in the list, send itself a message to terminate and wait for itself to terminate. If it wasn't written in a way which prioritised incoming messages over the wait state ...


If you stretch the definition of the term deadlock: a single thread can find itself blocked on a non-reentrant lock that it took earlier.


When a thread enters the synchronized block, it checks if the current thread is the owner of the lock, and if it is, the the thread just proceeds without waiting.

So I don't think it is possible.


I know this is a old post. Here is another example how it could happen if your code has interaction with external resources:

I have a thread, that open a database connection, start a transactionA, and begin update. The same thread, open another connection, start another transactionB. However, because transactionA hasn't committed yet, and it has database table locked, transactionB happens to access this locked table, so it has to wait

In the end the same thread is block by itself because it opened up more than one database connection.


This happened a lot in the application that I work with because there are many modules in the application, and a thread can run though many methods. These methods opens their own connections. Since we had different developers write their code, they may not see the how their code are begin called, and therefore couldn't see the overall database transactions that opened by the application.


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

Although the comments here are being pedantic about "deadlock" happening if at least two threads/actions are competing for the same resource...I think the spirit of this question was to discuss a need for Reentrant lock - especially in context of "recursive" locking

Here's an example in python (I am certain the concept stays the same in Java): If you change the RLock to Lock (i.e. reentrant lock to lock, the thread will hang)

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

ReferenceURL : https://stackoverflow.com/questions/3493441/is-it-possible-for-a-thread-to-deadlock-itself

반응형