본문 바로가기

Programming/Java

JAVA 데드락(Deadlock)과 이를 방지하는 방법 (교착상태)

반응형

데드락(교착 상태)란?

데드락(Deadlock)은 둘 이상의 스레드가 서로가 점유한 자원을 기다리며 무한히 대기하게 되는 상태를 말한다. 한국어로는 일반적으로 "교착 상태"라고 하며, 다중 스레드 환경에서 자주 발생할 수 있는 대표적인 병목 현상 중 하나이다.


1. 데드락 발생 조건 (Coffman Conditions)

데드락은 다음 네 가지 조건이 모두 충족될 때 발생한다:

  1. 상호 배제 (Mutual Exclusion): 자원은 한 번에 하나의 스레드만 사용할 수 있다.
  2. 점유 및 대기 (Hold and Wait): 자원을 점유한 상태에서 다른 자원을 기다린다.
  3. 비선점 (No Preemption): 자원을 강제로 회수할 수 없다.
  4. 순환 대기 (Circular Wait): 스레드들이 자원을 서로 기다리며 원형 대기를 형성한다.

2. 데드락 예시 (JAVA)

아래 코드는 AB라는 객체를 두 개의 스레드가 교차로 접근하면서 데드락이 발생할 수 있는 상황을 보여준다.

class A {
    synchronized void methodA(B b) {
        System.out.println("Thread 1: Locked A");
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        b.last();
    }
    synchronized void last() {
        System.out.println("Thread 1: Executing last");
    }
}

class B {
    synchronized void methodB(A a) {
        System.out.println("Thread 2: Locked B");
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        a.last();
    }
    synchronized void last() {
        System.out.println("Thread 2: Executing last");
    }
}

public class DeadlockExample {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();

        Thread t1 = new Thread(() -> a.methodA(b));
        Thread t2 = new Thread(() -> b.methodB(a));

        t1.start();
        t2.start();
    }
}

3. 데드락 방지 방법 및 예시

3.1 자원 요청 순서 지정 (Resource Ordering)

스레드들이 자원을 항상 일정한 순서로 요청하도록 강제하면, 순환 대기를 원천 차단할 수 있다.

// 항상 A를 먼저 잠그고 B를 나중에 잠그도록 설계
synchronized (lockA) {
    synchronized (lockB) {
        // 안전하게 작업 수행
    }
}

 

3.2 타임아웃 사용 (tryLock)

ReentrantLocktryLock()을 사용하면, 일정 시간 안에 락을 획득하지 못할 경우 포기할 수 있어 데드락을 방지할 수 있다.

Lock lockA = new ReentrantLock();
Lock lockB = new ReentrantLock();

if (lockA.tryLock(100, TimeUnit.MILLISECONDS)) {
    try {
        if (lockB.tryLock(100, TimeUnit.MILLISECONDS)) {
            try {
                // 작업 수행
            } finally {
                lockB.unlock();
            }
        }
    } finally {
        lockA.unlock();
    }
}

 

3.3 스레드 실행 순서 제어 (join() 활용)

join()을 이용하면 특정 스레드가 종료될 때까지 다른 스레드가 기다리게 할 수 있습니다. 이를 통해 자원 접근 순서를 통제할 수 있어 데드락 가능성을 줄일 수 있다.

Thread t1 = new Task1();
Thread t2 = new Task2();

t1.start();
t1.join(); // t2는 t1이 끝난 후 실행

t2.start();

 

3.4 비차단 설계 (Non-blocking Design)

락을 아예 사용하지 않고, 상태 기반 조건문 등을 통해 자원 접근을 동기화하는 방식이다. 자바의 AtomicInteger 같은 원자 클래스나 CAS(Compare-And-Swap) 기법을 활용할 수 있다.

 

3.4 데드락 감지 및 복구

자바 자체에서는 데드락 감지 기능이 내장되어 있지 않지만, ThreadMXBean 등의 JMX 도구를 사용하여 데드락을 탐지할 수 있으며, 필요한 경우 관리자 권한으로 프로세스를 재시작하거나 해당 스레드를 종료시켜 복구할 수 있다.


  • 자바에서는 synchronized, Lock, join() 등을 사용할 때 자원 접근 순서와 타이밍에 주의해야 한다.
  • 데드락을 방지하려면 사전 설계 단계에서부터 일관된 자원 접근 규칙을 정의하는 것이 중요하다.
반응형