본문 바로가기
CS

Deadlock과 Livelock 개념 정립 with Java

by 개미가되고싶은사람 2026. 1. 25.

멀티스레드 환경이나 분산 시스템에 리소스 관련 용어 중 가장 대표적인 것이 바로 데드락라이브락입니다.

 

두 용어의 네이밍으로만 봐서 어느정도 이해할 수 있습니다. 하지만 개발자 관점에서 어떠한 차이점이 있는지 분명하게 알아야 되기 때문에 이 두 용어에 대해서 알아보겠습니다. 

 

데드락 (Deadlock)

데드락은 두 개 이상의 작업이 서로 상대방의 작업이 끝나기만을 기다리느라 대기 상태로 멈춰버린 상태를 의미합니다. 조금 풀어서 이야기 하면 자신이 가진 자원은 절대 놓지 않으면서 상대방이 가진 자원만 얻으려고 버티다 보니, 결국 무한 대기에 빠져 아무런 작업을 못하게 되는 상태입니다. 시스템적으로 설명하면 스레드가 Blocked / Waiting 상태에 머물며, CPU 사용률이 0%에 수렴하여 시스템이 멈춘(대기) 상태라고 할 수 있습니다.

 

 

코드 예시

public class DeadlockTest {
    private final Lock lockA = new ReentrantLock();
    private final Lock lockB = new ReentrantLock();

    @Test
    void testDeadlock() throws InterruptedException {
    
        Thread t1 = new Thread(() -> {
            lockA.lock();
            try {
                System.out.println("Thread 1: lockA 획득, lockB 기다리는 중");
                Thread.sleep(5000); 
                lockB.lock();
                
                try {
                    System.out.println("Thread 1: 모든 락 획득");
                } finally {
                    lockB.unlock();
                }
                
            } catch (InterruptedException e) {
                e.printStackTrace();
                
            } finally {
                lockA.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            lockB.lock();
            try {
                System.out.println("Thread 2: lockB 획득, lockA 기다리는 중");
                
                Thread.sleep(5000);
                lockA.lock();
                try {
                    System.out.println("Thread 2: 모든 락 획득");
                } finally {
                    lockA.unlock();
                }
                
            } catch (InterruptedException e) {
                e.printStackTrace();
            
            } finally {
                lockB.unlock();
            }
        });

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

        System.out.println("----------------- 데드락 발생 -----------");
        t1.join(); // 여기서 무한 대기
        System.out.println("----------------- 나오면 안됨 -----------");
        t2.join();
    }
}

 

t1이 lockA를 선점한 채 lockB를 기다리고, 동시에 t2가 lockB를 쥔 상태로 lockA를 기다리게 되면, 두 스레드 모두 자신이 가진 락은 절대 놓지 않은 채 상대방의 자원만 요구하는 무한 대기 상태에 빠지게 됩니다. 위에 사진에서 볼 수 있듯이 계속 대기 상태에 빠져있는 걸 볼 수 있습니다.

 

 

라이브 락(Livelock)

라이브락은 그 이름처럼 겉보기에는 살아있는 것처럼 보이는 상태입니다. 서로에게 자원을 양보하거나 실패한 작업을 끊임없이 재시도하지만 그 과정이 계속 실패하면서 실질적인 작업은 전혀 진전되지 못한 채 늪에 빠진 상태입니다. 결과적으로 분주하게 움직이고는 있으나 아무것도 이루지 못하는 상태이기에 완전히 멈춰버리는 데드락과 달리 CPU 사용량이 매우 높게 치솟게 됩니다. 결국 자원은 자원대로 소모하면서 시스템에 부하만 가중시키기 때문에 데드락보다 더  위험한 상태라고 볼 수 있습니다.

 

 

코드 예시 

public class LivelockTest {
    
    class Member {
        private String name;
        private boolean active;

        public Member(String name, boolean active) {
            this.name = name;
            this.active = active;
        }

        public synchronized void testMethod(Member otherMember, OriginResource resource) {
            while (active) {
                // 리소스를 소유하고 있지 않다면 잠시 대기 후 재시도
                if (resource.getOrigin() != this) {
                    try { Thread.sleep(10); } catch (InterruptedException e) {}
                    continue;
                }

                // 상대방이 active 상태면 리소스를 양보
                if (otherMember.active) {
                    System.out.println(name + ": " + otherMember.name + " 멤버에게 리소스를 양보");
                    resource.setOrigin(otherMember);
                    continue;
                }

                System.out.println(name + ": 작업 완료");

                active = false;
                resource.setOrigin(otherMember);
            }
        }
    }

    class OriginResource {
        private Member origin;
        public OriginResource(Member origin) { this.origin = origin; }
        public synchronized Member getOrigin() { return origin; }
        public synchronized void setOrigin(Member origin) { this.origin = origin; }
    }

    @Test
    void testLivelock() throws InterruptedException {
        Member m1 = new Member("페이커", true);
        Member m2 = new Member("케리아", true);
        
        OriginResource resource = new OriginResource(m1);

        Thread t1 = new Thread(() -> m1.testMethod(m2, resource));
        Thread t2 = new Thread(() -> m2.testMethod(m1, resource));

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

        t1.join(); // 작업 관리자로 보거나 다른 방법으로 cpu 사용량을 확인하면 됨
        t2.join();
    }
}

 

 

 

 

해결 방법

 

데드락

데드락은 한 번 발생하면 외부 개입 없이는 풀리지 않으므로, 애초에 발생하지 않도록 설계하는 것이 중요합니다.

  • 자원 순서 정렬 순서대로 락 획득
  • 교착 상태 감지 후 하나를 강제 종료
  • 타임아웃을 설정해 일정 시간 지나면 종료

 

 

라이브 락

  • Random Backoff(임의 시간 대기): 양보와 재시도가 겹치지 않도록 스레드마다 대기 시간을 랜덤하게 부여 (Ethernet에 사용)
  • 재시도 횟수 제한

 

 

 

 

'CS' 카테고리의 다른 글

Cache vs MQ 성능 개선을 위해 무엇을 도입해야 할까??  (0) 2026.01.26