최근 인터뷰에서이 질문을 받았습니다.
인터리빙이 잘못되면 교착 상태가 발생한다고 대답했지만 면접관은 인터리빙에 관계없이 항상 교착 상태가되는 프로그램을 작성할 수 있다고 주장했다.
그런 프로그램을 작성할 수 있습니까? 저에게 그런 예제 프로그램을 알려줄 수 있습니까?
최근 인터뷰에서이 질문을 받았습니다.
인터리빙이 잘못되면 교착 상태가 발생한다고 대답했지만 면접관은 인터리빙에 관계없이 항상 교착 상태가되는 프로그램을 작성할 수 있다고 주장했다.
그런 프로그램을 작성할 수 있습니까? 저에게 그런 예제 프로그램을 알려줄 수 있습니까?
답변:
업데이트 : 이 질문은 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()
{ }
}
여기서 래치는 각 스레드가 다른 스레드를 잠그려고 할 때 두 잠금이 모두 유지되도록합니다.
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을 실행하는 것이 흥미 롭습니다.
sleep
적절한 래치로 교체 하겠습니다. 이론적으로는 여기에 경쟁 조건이 있습니다. 0.5 초면 충분하다고 거의 확신 할 수 있지만 인터뷰 작업에는 적합하지 않습니다.
교착 상태는 스레드 (또는 플랫폼에서 실행 단위를 호출하는 모든 항목)가 리소스를 획득 할 때 발생 하며, 여기서 각 리소스는 한 번에 하나의 스레드 만 보유 할 수 있으며 보류를 선점 할 수없는 방식으로 해당 리소스를 보유합니다. 교착 상태에있는 각 스레드가 다른 스레드가 보유한 일부 리소스를 획득하기 위해 대기하는 것처럼 스레드간에 "원형"관계가 있습니다.
따라서 교착 상태를 피하는 쉬운 방법은 리소스에 전체 순서 를 지정 하고 리소스는 스레드 에 의해서만 순서대로 획득된다는 규칙을 부과하는 것입니다. . 반대로 교착 상태는 리소스를 획득하지만 순서대로 획득하지는 않는 스레드를 실행하여 의도적으로 생성 할 수 있습니다. 예를 들면 :
두 개의 스레드, 두 개의 자물쇠. 첫 번째 스레드는 특정 순서로 잠금을 획득하려는 루프를 실행하고 두 번째 스레드는 반대 순서로 잠금을 획득하려는 루프를 실행합니다. 각 스레드는 성공적으로 잠금을 획득 한 후 두 잠금을 모두 해제합니다.
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();
}
}
다음은 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();
}
}
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();
}
}
Object invokeAndWait(Callable task)
메소드 를 노출하는 라운드 로빈 스케줄러를 작성할 수 있습니다 . 그리고 모두가 Callable t1
이다 상관이 invokeAndWait()
에 대한 Callable t2
반환하기 전에 수명 시간 동안, 그리고 그 반대의 경우도 마찬가지입니다.
sleep
는 지루합니다. 5 초 동안 스레드가 시작되지 않을 것이라고 생각하지만 어쨌든 경쟁 조건입니다. sleep()
경쟁 조건을 해결하는 데 의존 할 프로그래머를 고용하고 싶지 않습니다. :)
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");
}
}
그것은 당신이 얻을 수있는 가장 간단한 인터뷰 작업이 아닙니다. 제 프로젝트에서 그것은 하루 종일 팀의 작업을 마비 시켰습니다. 프로그램을 중지시키는 것은 매우 쉽지만 스레드 덤프가 다음과 같은 내용을 작성 하는 상태로 만드는 것은 매우 어렵습니다 .
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"
로 교체합니다 .
LOCKED
와 waiting to lock
, 당신은 아침 식사를하는 동안 읽을 뭔가 미묘한 없습니다. 하지만 아마 당신 말이 맞을 것입니다. 다시 말하겠습니다.
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
.
} catch (InterruptedException ex) { continue; }
... 아름다운
여기에 자바의 예가 있습니다.
http://baddotrobot.com/blog/2009/12/24/deadlock/
납치범이 피해자가 현금을받을 때까지 포기를 거부하지만 협상가가 피해자를 얻을 때까지 현금을 포기하지 않을 때 교착 상태에 빠진다.
간단한 검색으로 다음 코드를 얻었습니다.
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();
}
}
출처 : 교착 상태
다음은 잠금을 유지하는 한 스레드가 동일한 잠금을 원하는 다른 스레드를 시작한 다음 시작이 완료 될 때까지 계속 대기하는 샘플입니다.
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();
}
}
다음은 예입니다.
두 개의 스레드가 실행 중이며 각각 다른 스레드가 잠금을 해제하기를 기다리고 있습니다.
공용 클래스 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);
}
}