반드시 교착 상태에 빠질 프로그램을 작성하십시오.


86

최근 인터뷰에서이 질문을 받았습니다.

인터리빙이 잘못되면 교착 상태가 발생한다고 대답했지만 면접관은 인터리빙에 관계없이 항상 교착 상태가되는 프로그램을 작성할 수 있다고 주장했다.

그런 프로그램을 작성할 수 있습니까? 저에게 그런 예제 프로그램을 알려줄 수 있습니까?


3
면접관은 확실히 어리석은 사람입니다.
— Lion

23
면접관은 확실히 어리석은 사람이 아닙니다. 주제에 대한 완전한 이해는 극지의 경우를 설명 할 수 있어야한다는 것을 의미합니다. 프로그램을 절대 잠그지 않고 항상 잠그도록 만드는 것입니다.
— Yuriy Zubarev 2012

답변:


100

업데이트 : 이 질문은 2013 년 1 월 내 블로그의 주제였습니다 . 좋은 질문에 감사드립니다!


스레드가 어떻게 예약 되더라도 항상 교착 상태가되는 프로그램을 어떻게 작성할 수 있습니까?

다음은 C #의 예입니다. 참고 프로그램이 더 잠금없이 공유 데이터가없는 것으로 보인다. 단일 지역 변수와 3 개의 문만 있지만 100 % 확실하게 교착 상태가됩니다. 확실하게 교착 상태가되는 더 간단한 프로그램을 내놓기 란 쉽지 않을 것입니다.

독자에게 연습 # 1 : 교착 상태가 어떻게 발생하는지 설명하십시오. (답변은 댓글에 있습니다.)

독자에게 연습 # 2 : Java에서 동일한 교착 상태를 보여줍니다. (답변 : https://stackoverflow.com/a/9286697/88656 )

class MyClass
{
  static MyClass() 
  {
    // Let's run the initialization on another thread!
    var thread = new System.Threading.Thread(Initialize);
    thread.Start();
    thread.Join();
  }

  static void Initialize() 
  { /* TODO: Add initialization code */ }

  static void Main() 
  { }
}

4
이론적 인 C #에 대한 나의 지식은 제한적이지만 클래스 로더는 코드가 Java 에서처럼 단일 스레드로 실행된다는 것을 보장한다고 가정합니다. Java Puzzlers에 비슷한 예가 있다고 확신합니다.
— Voo

11
@Voo : 당신은 좋은 기억이 있습니다. "Java Puzzlers"의 공동 저자 인 Neal Gafter는 몇 년 전 오슬로 개발자 컨퍼런스에서 열린 "C # Puzzlers"강연에서이 코드의 다소 난독 화 된 버전을 발표했습니다.
— Eric Lippert

41
@Lieven : 정적 생성자는 한 번만 실행해야 하며 클래스의 정적 메서드를 처음 호출 하기 전에 실행해야합니다 . Main은 정적 메서드이므로 기본 스레드는 정적 ctor를 호출합니다. 한 번만 실행되도록하기 위해 CLR은 정적 ctor가 완료 될 때까지 해제되지 않는 잠금을 해제합니다. ctor가 새 스레드를 시작하면 해당 스레드도 정적 메서드를 호출하므로 CLR은 잠금을 가져와 ctor를 실행해야하는지 확인합니다. 한편 메인 스레드는 차단 된 스레드를 "결합"하고 이제 교착 상태가됩니다.
— Eric Lippert

33
@artbristol : 저는 한 줄의 Java 코드를 작성한 적이 없습니다. 지금 시작할 이유가 없습니다.
— Eric Lippert

4
오, 연습 # 2에 대한 답이 있다고 생각했습니다. Java 질문에 답 해주신 것을 축하드립니다.
— artbristol

27

여기서 래치는 각 스레드가 다른 스레드를 잠그려고 할 때 두 잠금이 모두 유지되도록합니다.

import java.util.concurrent.CountDownLatch;

public class Locker extends Thread {

   private final CountDownLatch latch;
   private final Object         obj1;
   private final Object         obj2;

   Locker(Object obj1, Object obj2, CountDownLatch latch) {
      this.obj1 = obj1;
      this.obj2 = obj2;
      this.latch = latch;
   }

   @Override
   public void run() {
      synchronized (obj1) {

         latch.countDown();
         try {
            latch.await();
         } catch (InterruptedException e) {
            throw new RuntimeException();
         }
         synchronized (obj2) {
            System.out.println("Thread finished");
         }
      }

   }

   public static void main(String[] args) {
      final Object obj1 = new Object();
      final Object obj2 = new Object();
      final CountDownLatch latch = new CountDownLatch(2);

      new Locker(obj1, obj2, latch).start();
      new Locker(obj2, obj1, latch).start();

   }

}

스레드 탭에서 교착 상태를 올바르게 표시하는 jconsole을 실행하는 것이 흥미 롭습니다.


3
지금까지는 최고이지만 sleep적절한 래치로 교체 하겠습니다. 이론적으로는 여기에 경쟁 조건이 있습니다. 0.5 초면 충분하다고 거의 확신 할 수 있지만 인터뷰 작업에는 적합하지 않습니다.
— .01.16.13

25

교착 상태는 스레드 (또는 플랫폼에서 실행 단위를 호출하는 모든 항목)가 리소스를 획득 할 때 발생 하며, 여기서 각 리소스는 한 번에 하나의 스레드 만 보유 할 수 있으며 보류를 선점 할 수없는 방식으로 해당 리소스를 보유합니다. 교착 상태에있는 각 스레드가 다른 스레드가 보유한 일부 리소스를 획득하기 위해 대기하는 것처럼 스레드간에 "원형"관계가 있습니다.

따라서 교착 상태를 피하는 쉬운 방법은 리소스에 전체 순서 를 지정 하고 리소스는 스레드 에 의해서만 순서대로 획득된다는 규칙을 부과하는 것입니다. . 반대로 교착 상태는 리소스를 획득하지만 순서대로 획득하지는 않는 스레드를 실행하여 의도적으로 생성 할 수 있습니다. 예를 들면 :

두 개의 스레드, 두 개의 자물쇠. 첫 번째 스레드는 특정 순서로 잠금을 획득하려는 루프를 실행하고 두 번째 스레드는 반대 순서로 잠금을 획득하려는 루프를 실행합니다. 각 스레드는 성공적으로 잠금을 획득 한 후 두 잠금을 모두 해제합니다.

public class HighlyLikelyDeadlock {
    static class Locker implements Runnable {
        private Object first, second;

        Locker(Object first, Object second) {
            this.first = first;
            this.second = second;
        }

        @Override
        public void run() {
            while (true) {
                synchronized (first) {
                    synchronized (second) {
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            }
        }
    }

    public static void main(final String... args) {
        Object lock1 = new Object(), lock2 = new Object();
        new Thread(new Locker(lock1, lock2), "Thread 1").start();
        new Thread(new Locker(lock2, lock1), "Thread 2").start();
    }
}

이제이 질문에는 가능성 과 확실성 의 차이를 지적하는 몇 가지 의견이 있습니다. 에 교착 상태 의 있습니다. 어떤 의미에서 구별은 학문적 문제입니다. 실용적인 관점에서 필자는 위에서 작성한 코드와 교착 상태가 아닌 실행중인 시스템을보고 싶습니다. :)

그러나 인터뷰 질문은 때때로 학술적 일 수 있으며이 SO 질문에는 제목에 "확실히"라는 단어가 포함되어 있으므로 다음은 확실히 교착 상태 가되는 프로그램입니다 . 두 개의 Locker오브젝트가 작성되고 각각에 두 개의 잠금이 부여 CountDownLatch되고 스레드 간 동기화에 사용됩니다. 각각 Locker은 첫 번째 잠금을 잠근 다음 래치를 한 번 카운트 다운합니다. 두 스레드가 잠금을 획득하고 래치를 카운트 다운하면 래치 장벽을지나 두 번째 잠금을 획득하려고 시도하지만 각 경우 다른 스레드는 이미 원하는 잠금을 보유하고 있습니다. 이 상황으로 인해 특정 교착 상태가 발생합니다.

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CertainDeadlock {
    static class Locker implements Runnable {
        private CountDownLatch latch;
        private Lock first, second;

        Locker(CountDownLatch latch, Lock first, Lock second) {
            this.latch = latch;
            this.first = first;
            this.second = second;
        }

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            try {
                first.lock();
                latch.countDown();
                System.out.println(threadName + ": locked first lock");
                latch.await();
                System.out.println(threadName + ": attempting to lock second lock");
                second.lock();
                System.out.println(threadName + ": never reached");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(final String... args) {
        CountDownLatch latch = new CountDownLatch(2);
        Lock lock1 = new ReentrantLock(), lock2 = new ReentrantLock();
        new Thread(new Locker(latch, lock1, lock2), "Thread 1").start();
        new Thread(new Locker(latch, lock2, lock1), "Thread 2").start();
    }
}

3
Linus의 인용에 대해 죄송합니다. "Talk is cheap. show me the code."— 좋은 작업이고 생각보다 놀랍도록 어렵습니다.
— alf

2
교착없이이 코드를 실행할 수 있습니다
— 블라디미르 Zhilyaev

1
좋아, 너희들은 잔인하지만 이제는 이것이 완전한 대답이라고 생각합니다.
— Greg Mattes

@GregMattes 감사합니다 :) +1 외에는 아무것도 추가 할 수 없습니다. 즐거운 시간이 되셨기를 바랍니다. :)
— alf

15

다음은 Eric Lippert의 것을 따르는 Java 예제입니다.

public class Lock implements Runnable {

    static {
        System.out.println("Getting ready to greet the world");
        try {
            Thread t = new Thread(new Lock());
            t.start();
            t.join();
        } catch (InterruptedException ex) {
            System.out.println("won't see me");
        }
    }

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }

    public void run() {           
        Lock lock = new Lock();      
    }

}

4
실행 방법에서 조인을 사용하는 것은 약간의 오해의 소지가 있다고 생각합니다. 이것은 "new Lock ()"문에 의해 교착 상태가 발생하는 동안 교착 상태를 얻기 위해 정적 블록에있는 것 외에 다른 조인이 필요함을 나타냅니다. C # 예제와 같은 정적 메서드를 사용하여 다시 작성합니다. stackoverflow.com/a/16203272/2098232
— luke657

당신의 예를 설명해 주시겠습니까?
— gstackoverflow

내 실험에 따르면 t.join (); 내부 실행 () 메소드는 중복
— gstackoverflow

나는 이해 방지 중복 코드 제거
— gstackoverflow

11

다음은 문서 의 예 입니다.

public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse =
            new Friend("Alphonse");
        final Friend gaston =
            new Friend("Gaston");
        new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
}

2
+1 자바 튜토리얼을 연결합니다.
— mre

4
"매우 가능성이 높음"은 "확실히 교착 상태에 빠질 것"에 충분하지 않습니다
— alf

1
@alf 오, 그러나 근본적인 문제는 여기에서 아주 잘 설명됩니다. Object invokeAndWait(Callable task)메소드 를 노출하는 라운드 로빈 스케줄러를 작성할 수 있습니다 . 그리고 모두가 Callable t1이다 상관이 invokeAndWait()에 대한 Callable t2반환하기 전에 수명 시간 동안, 그리고 그 반대의 경우도 마찬가지입니다.
— user268396

2
@ user268396 음, 근본적인 문제는 사소하고 지루합니다. :) 작업의 요점은 보장 된 교착 상태를 얻는 것이 놀라 울 정도로 어렵다는 것을 알아 내거나 이해한다는 것을 증명하는 것입니다 (비동기 세계에서 모든 것을 보장하는 것뿐만 아니라 ).
— alf

4
@bezz sleep는 지루합니다. 5 초 동안 스레드가 시작되지 않을 것이라고 생각하지만 어쨌든 경쟁 조건입니다. sleep()경쟁 조건을 해결하는 데 의존 할 프로그래머를 고용하고 싶지 않습니다. :)
— alf

9

Eric Lippert ( https://stackoverflow.com/a/9286697/2098232) 가 게시 한 Yuriy Zubarev의 Java 버전의 교착 상태 예제를 C # 버전과 더 비슷하게 다시 작성했습니다 . Java의 초기화 블록이 C # 정적 생성자와 유사하게 작동하고 먼저 잠금을 획득하면 교착 상태를 얻기 위해 조인 메서드를 호출하기 위해 다른 스레드가 필요하지 않습니다. 원래 C #과 같이 Lock 클래스에서 일부 정적 메서드 만 호출하면됩니다. 예. 결과 교착 상태가이를 확인하는 것 같습니다.

public class Lock {

    static {
        System.out.println("Getting ready to greet the world");
        try {
            Thread t = new Thread(new Runnable(){

                @Override
                public void run() {
                    Lock.initialize();
                }

            });
            t.start();
            t.join();
        } catch (InterruptedException ex) {
            System.out.println("won't see me");
        }
    }

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }

    public static void initialize(){
        System.out.println("Initializing");
    }

}

run 메서드에서 Lock.initialize () 주석을 달면 교착 상태가되지 않는 이유는 무엇입니까? 초기화 메서드는 아무 작업도하지 않습니다 ??
— Aequitas 2015

@Aequitas는 추측에 불과하지만 방법은 최적화 될 수 있습니다. 확실하지 그 스레드로 작업 할 방법에 대해
— 데이브 Cousineau

5

그것은 당신이 얻을 수있는 가장 간단한 인터뷰 작업이 아닙니다. 제 프로젝트에서 그것은 하루 종일 팀의 작업을 마비 시켰습니다. 프로그램을 중지시키는 것은 매우 쉽지만 스레드 덤프가 다음과 같은 내용을 작성 하는 상태로 만드는 것은 매우 어렵습니다 .

Found one Java-level deadlock:
=============================
"Thread-2":
  waiting to lock monitor 7f91c5802b58 (object 7fb291380, a java.lang.String),
  which is held by "Thread-1"
"Thread-1":
  waiting to lock monitor 7f91c6075308 (object 7fb2914a0, a java.lang.String),
  which is held by "Thread-2"

Java stack information for the threads listed above:
===================================================
"Thread-2":
    at uk.ac.ebi.Deadlock.run(Deadlock.java:54)
    - waiting to lock <7fb291380> (a java.lang.String)
    - locked <7fb2914a0> (a java.lang.String)
    - locked <7f32a0760> (a uk.ac.ebi.Deadlock)
    at java.lang.Thread.run(Thread.java:680)
"Thread-1":
    at uk.ac.ebi.Deadlock.run(Deadlock.java:54)
    - waiting to lock <7fb2914a0> (a java.lang.String)
    - locked <7fb291380> (a java.lang.String)
    - locked <7f32a0580> (a uk.ac.ebi.Deadlock)
    at java.lang.Thread.run(Thread.java:680)

따라서 목표는 JVM이 교착 상태로 간주하는 교착 상태를 얻는 것입니다. 분명히, 같은 해결책은 없습니다.

synchronized (this) {
    wait();
}

실제로 영원히 멈출지라도 그런 의미에서 작동합니다. 경쟁 조건에 의존하는 것도 좋은 생각이 아닙니다. 인터뷰 중에 대부분의 경우 효과가있는 것이 아니라 입증 된 효과가있는 것을 보여주고 싶기 때문입니다.

이제 sleep()솔루션은 작동하지 않지만 공정하지 않은 상황을 상상하기 어렵다는 의미에서 괜찮습니다 (우리는 공정한 스포츠를하고 있지 않습니까?). @artbristol의 솔루션 (마인은 동일하고 모니터와 다른 개체)은 좋지만 길고 새로운 동시성 기본 요소를 사용하여 스레드를 올바른 상태로 가져옵니다.

public class Deadlock implements Runnable {
    private final Object a;
    private final Object b;
    private final static CountDownLatch latch = new CountDownLatch(2);

    public Deadlock(Object a, Object b) {
        this.a = a;
        this.b = b;
    }

    public synchronized static void main(String[] args) throws InterruptedException {
        new Thread(new Deadlock("a", "b")).start();
        new Thread(new Deadlock("b", "a")).start();
    }

    @Override
    public void run() {
        synchronized (a) {
            latch.countDown();
            try {
                latch.await();
            } catch (InterruptedException ignored) {
            }
            synchronized (b) {
            }
        }
    }
}

나는 기억한다 synchronized - 단지 솔루션 (의견과 수입 제외) 코드의 11..13 라인에 맞는,하지만 실제 트릭을 기억 못하고있다. 내가 할 경우 업데이트됩니다.

업데이트 : 여기에 대한 추악한 해결책이 있습니다 synchronized.

public class Deadlock implements Runnable {
    public synchronized static void main(String[] args) throws InterruptedException {
        synchronized ("a") {
            new Thread(new Deadlock()).start();
            "a".wait();
        }
        synchronized ("") {
        }
    }

    @Override
    public void run() {
        synchronized ("") {
            synchronized ("a") {
                "a".notifyAll();
            }
            synchronized (Deadlock.class) {
            }
        }
    }
}

래치를 객체 모니터 (객체로 사용) "a"로 교체합니다 .


Hum 나는 그것이 공정한 인터뷰 작업이라고 생각합니다. Java의 교착 상태와 잠금을 실제로 이해하도록 요청합니다. 나는 일반적인 아이디어도 그렇게 어렵지 않다고 생각합니다 (두 스레드가 모두 첫 번째 리소스를 잠근 후에 만 ​​계속 될 수 있는지 확인). CountdownLatch를 기억해야합니다.하지만 면접관으로서 저는 그 부분에서 인터뷰 대상자를 돕겠습니다. 그가 필요한 것을 정확히 설명 할 수 있다면 (대부분의 개발자가 필요로하는 수업이 아니며 인터뷰에서 Google을 검색 할 수 없습니다). 인터뷰에 대한 흥미로운 질문을 받고 싶습니다!
— Voo

@Voo 우리가 그것을 가지고 놀 때 JDK에는 래치가 없었기 때문에 모두 수작업이었습니다. 그리고 사이의 차이 LOCKED와 waiting to lock, 당신은 아침 식사를하는 동안 읽을 뭔가 미묘한 없습니다. 하지만 아마 당신 말이 맞을 것입니다. 다시 말하겠습니다.
— alf

4

이 C # 버전은 자바가 꽤 비슷해야한다고 생각합니다.

static void Main(string[] args)
{
    var mainThread = Thread.CurrentThread;
    mainThread.Join();

    Console.WriteLine("Press Any key");
    Console.ReadKey();
}

2
잘 했어! console문 을 제거하면 교착 상태를 만드는 가장 짧은 C # 프로그램 입니다. 전체 Main함수를 간단히 Thread.CurrentThread.Join();.
— RBT

3
import java.util.concurrent.CountDownLatch;

public class SO8880286 {
    public static class BadRunnable implements Runnable {
        private CountDownLatch latch;

        public BadRunnable(CountDownLatch latch) {
            this.latch = latch;
        }

        public void run() {
            System.out.println("Thread " + Thread.currentThread().getId() + " starting");
            synchronized (BadRunnable.class) {
                System.out.println("Thread " + Thread.currentThread().getId() + " acquired the monitor on BadRunnable.class");
                latch.countDown();
                while (true) {
                    try {
                        latch.await();
                    } catch (InterruptedException ex) {
                        continue;
                    }
                    break;
                }
            }
            System.out.println("Thread " + Thread.currentThread().getId() + " released the monitor on BadRunnable.class");
            System.out.println("Thread " + Thread.currentThread().getId() + " ending");
        }
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[2];
        CountDownLatch latch = new CountDownLatch(threads.length);
        for (int i = 0; i < threads.length; ++i) {
            threads[i] = new Thread(new BadRunnable(latch));
            threads[i].start();
        }
    }
}

각 스레드가 다른 스레드에 대한 장벽에서 대기하고 있기 때문에 프로그램은 항상 교착 상태가되지만 장벽을 기다리려면 스레드가 모니터를 켜고 있어야합니다 BadRunnable.class.


3
} catch (InterruptedException ex) { continue; }... 아름다운
— artbristol

2

여기에 자바의 예가 있습니다.

http://baddotrobot.com/blog/2009/12/24/deadlock/

납치범이 피해자가 현금을받을 때까지 포기를 거부하지만 협상가가 피해자를 얻을 때까지 현금을 포기하지 않을 때 교착 상태에 빠진다.


그 구현은 주어진 것과 관련이 없습니다. 일부 코드가 누락 된 것으로 보입니다. 그러나 당신이 표현하는 일반적인 아이디어는 교착 상태로 이어지는 자원 경합에 관한 한 정확합니다.
— 마스터 치프

이 예제는 교육적이므로 왜 적절하지 않은 것으로 해석하는지 궁금합니다. 누락 된 코드는 메서드 이름이 도움이되어야하는 빈 메서드입니다 (간결성을 위해 표시되지 않음)
— Toby

1

간단한 검색으로 다음 코드를 얻었습니다.

public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse =
            new Friend("Alphonse");
        final Friend gaston =
            new Friend("Gaston");
        new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
}

출처 : 교착 상태


3
"확실히 교착 상태에 들어갈 것"은 "그것은 매우 가능성이 높습니다은"좋은 것만으로는 충분하지 않습니다
— alf

1

다음은 잠금을 유지하는 한 스레드가 동일한 잠금을 원하는 다른 스레드를 시작한 다음 시작이 완료 될 때까지 계속 대기하는 샘플입니다.

class OuterTask implements Runnable {
    private final Object lock;

    public OuterTask(Object lock) {
        this.lock = lock;
    }

    public void run() {
        System.out.println("Outer launched");
        System.out.println("Obtaining lock");
        synchronized (lock) {
            Thread inner = new Thread(new InnerTask(lock), "inner");
            inner.start();
            try {
                inner.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class InnerTask implements Runnable {
    private final Object lock;

    public InnerTask(Object lock) {
        this.lock = lock;
    }

    public void run() {
        System.out.println("Inner launched");
        System.out.println("Obtaining lock");
        synchronized (lock) {
            System.out.println("Obtained");
        }
    }
}

class Sample {
    public static void main(String[] args) throws InterruptedException {
        final Object outerLock = new Object();
        OuterTask outerTask = new OuterTask(outerLock);
        Thread outer = new Thread(outerTask, "outer");
        outer.start();
        outer.join();
    }
}

0

다음은 예입니다.

두 개의 스레드가 실행 중이며 각각 다른 스레드가 잠금을 해제하기를 기다리고 있습니다.

공용 클래스 ThreadClass는 Thread {를 확장합니다.

String obj1,obj2;
ThreadClass(String obj1,String obj2){
    this.obj1=obj1;
    this.obj2=obj2;
    start();
}

public void run(){
    synchronized (obj1) {
        System.out.println("lock on "+obj1+" acquired");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("waiting for "+obj2);
        synchronized (obj2) {
            System.out.println("lock on"+ obj2+" acquired");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }


}

}

이것을 실행하면 교착 상태가 발생합니다.

public class SureDeadlock {

public static void main(String[] args) {
    String obj1= new String("obj1");
    String obj2= new String("obj2");

    new ThreadClass(obj1,obj2);
    new ThreadClass(obj2,obj1);


}

}

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.