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


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에는 래치가 없었기 때문에 모두 수작업이었습니다. 그리고 사이의 차이 LOCKEDwaiting 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.