
두 개 이상의 스레드가 서로 상대방이 보유한 리소스를 기다리며 무한정 대기하는 상태
예를 들어, 마치 좁은 복도에서 두 사람이 서로 비켜주기를 기다리면서 둘 다 움직이지 못하는 상황과 유사합니다. 이러한 상태가 계속 진행되어 둘 다 비켜주지 않으면 두 사람은 그 자리에서 정체됩니다.
각 프로세스나 스레드는 서로가 점유한 자원을 사용해야 하는데, 교착상태에 걸리게 되면 결국 아무일도 처리하지 못하고 멈춰버리게 됩니다. 이러한 상태에 빠지면 자원이 제대로 사용되지 않으므로, 시스템 전체의 성능을 일으킬 수 있습니다.
public class DeadlockExample {
private static final Object RESOURCE_A = new Object();
private static final Object RESOURCE_B = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (RESOURCE_A) {
System.out.println("Thread 1: 리소스 A 획득");
try { Thread.sleep(100); } catch (InterruptedException e) { }
synchronized (RESOURCE_B) { // 데드락 발생 지점
System.out.println("Thread 1: 리소스 B 획득");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (RESOURCE_B) {
System.out.println("Thread 2: 리소스 B 획득");
try { Thread.sleep(100); } catch (InterruptedException e) { }
synchronized (RESOURCE_A) { // 데드락 발생 지점
System.out.println("Thread 2: 리소스 A 획득");
}
}
});
thread1.start();
thread2.start();
}
}
위 코드에서 thread1은 RESOURCE_A를 먼저 획득한 후 RESOURCE_B를 획득하려고 하며, thread2는 RESOURCE_B를 먼저 획득한 후 RESOURCE_A를 획득하려고 합니다.
이로 인해 두 스레드는 서로가 보유한 리소스를 기다리면서 영원히 진행되지 못하는 교착 상태에 빠지게 됩니다.
교착 상태 예방은 교착 상태 발생의 4가지 필요 조건(상호 배제, 점유와 대기, 비선점, 순환 대기) 중 하나 이상을 제거하여 교착 상태 자체가 발생하지 않도록 하는 접근 방식입니다.
상호 배제 조건은 한 번에 하나의 프로세스만 자원을 사용할 수 있다는 조건입니다. 이 조건을 제거하는 것은 자원을 공유 가능하게 만드는 것입니다.
방법
점유와 대기 조건은 프로세스가 최소한 하나의 자원을 점유한 상태에서 다른 자원을 기다리는 상황을 말합니다.
방법
비선점 조건은 이미 할당된 자원을 강제로 빼앗을 수 없다는 조건입니다.
방법
순환 대기 조건은 프로세스들이 원형으로 자원을 기다리는 상황을 말합니다.
방법
해당 할당이 시스템을 불안전한 상태(교착 상태 가능성이 있는 상태)로 만들지 않는지 사전에 확인하는 전략입니다.
ex) 은행원 알고리즘
교착 상태 예방이나 회피가 불가능하거나 비효율적인 경우, 시스템은 교착 상태가 발생하도록 허용하고, 이를 감지하여 복구하는 방법을 사용할 수 있습니다.
Java에서의 교착 상태 감지
Java에서는 jstack 도구를 사용하여 스레드 덤프를 생성하고 교착 상태를 감지할 수 있습니다.
bash
Copy
jstack -l <Java 프로세스 ID>
이 명령은 스레드 덤프를 생성하며, 교착 상태가 감지되면 "Found one Java-level deadlock" 메시지와 함께 관련 스레드 정보를 표시합니다.
Java에서의 교착 상태 복구
Java에서 교착 상태를 자동으로 복구하는 메커니즘은 제공되지 않습니다. 교착 상태가 감지되면 일반적으로 다음과 같은 수동 조치가 필요합니다.