Java thread deadlock: 흔한 원인과 해결 순서
마지막 업데이트

Java thread deadlock: 흔한 원인과 해결 순서


Java 서비스가 진행을 멈춘 것처럼 보일 때는 진짜 thread deadlock 일 수도 있고, 비슷하게 보이는 heavy lock contention 이나, 소수의 blocked worker 뒤에 queue 가 막힌 상황일 수도 있습니다. 밖에서 보기에는 거의 비슷하지만, 실제 fix 방향은 완전히 다릅니다.

짧게 말하면 핵심은 이것입니다. 프로세스를 재시작하기 전에 true deadlock 인지 generic waiting 인지 먼저 구분해야 합니다. 진짜 deadlock 은 최소 두 개 이상의 실행 경로가 서로를 기다리는 cycle 이 있는 상태입니다. 반면 contention 이나 worker starvation 도 얼어붙은 것처럼 보일 수 있지만 thread state 는 다르게 나타납니다.

Java 전체 증상 분기부터 다시 보고 싶다면 Java Troubleshooting Guide 로 먼저 돌아가도 좋습니다.


thread state 와 lock ownership 부터 본다

deadlock 의 본질은 wait cycle 입니다.

그래서 request latency 만 보는 것보다 아래 자료가 훨씬 중요합니다.

  • thread dump
  • monitor ownership
  • lock ordering
  • 반복해서 나타나는 blocked/waiting 관계

재시작 전에 이 상태를 못 남기면, 실제 원인을 보여 주는 가장 중요한 증거를 잃는 경우가 많습니다.


deadlock 은 운영에서 어떻게 보이나

운영 환경에서는 보통 이렇게 보입니다.

  • 요청이 무기한 멈춘다
  • worker thread 가 같은 긴 시간 동안 계속 blocked 상태다
  • 소수의 stuck thread 뒤에서 queue 가 커진다
  • CPU 는 아주 높지 않은데 throughput 이 거의 없다
  • 여러 번 뜬 thread dump 에서 같은 waiting 관계가 반복된다

같은 thread 가 같은 lock 을 두고 여러 dump 에서 계속 기다리고 있다면, true cycle 가능성이 훨씬 커집니다.


흔한 원인

1. lock ordering 이 일관되지 않다

가장 전형적인 원인입니다.

한 코드 경로는 lock 을 한 순서로 잡고, 다른 경로는 같은 lock 을 반대 순서로 잡는 경우입니다.

synchronized (a) {
    synchronized (b) {
        // work
    }
}

다른 경로가 b 다음 a 를 잡으면, deadlock 은 특정 타이밍만 맞으면 바로 생길 수 있습니다.

2. 여러 lock 을 넓은 critical section 안에서 함께 잡는다

lock ordering 이 대부분 안전해 보여도, synchronized scope 가 넓으면 두 흐름이 나쁘게 겹칠 확률이 커집니다.

특히 아래일 때 더 위험합니다.

  • 여러 lock 을 nested 하게 잡는다
  • critical section 안에서 state mutation 외 일을 많이 한다
  • 여러 request path 가 같은 shared object 를 만진다

3. synchronized 안에서 blocking work 를 한다

I/O 나 느린 작업이 lock 안에 있다고 해서 자동으로 true deadlock 이 생기는 것은 아니지만, contention 과 waiting cascade 를 훨씬 악화시킵니다.

또 incident 를 해석하기 어렵게 만듭니다. 많은 thread 가 실제 cycle 이 아니라 한 stalled path 뒤에 줄을 서기 때문입니다.

4. worker starvation 이 진짜 원인을 가린다

실제로는 소수 thread 만 막혔는데 나머지가 뒤에 기다리면서 queue 가 계속 커질 수 있습니다.

이 경우 운영자는 deadlock 으로 보지만, 실제로는:

  • free worker 부족
  • same-pool nested wait
  • 하나의 stuck dependency path

같은 문제일 수 있습니다.

5. true deadlock 이 아니라 heavy contention 이다

아주 흔한 오진입니다.

strict 한 cyclic wait 는 없지만 contention 이 심해서 서비스가 멈춘 것처럼 보이는 경우입니다. 진행은 되지만 너무 느린 상태죠.


실전 점검 순서

1. incident 시점의 thread dump 를 잡는다

가능하면 한 번만이 아니라 여러 번 잡는 편이 좋습니다.

보고 싶은 것은 같은 thread 가 계속:

  • BLOCKED
  • WAITING
  • TIMED_WAITING

상태로 같은 lock 과 owner 주변에 머무는지입니다.

2. owner, waiting, blocked 관계를 찾는다

의심 lock 마다 아래를 찾으세요.

  • 누가 lock 을 가지고 있는가
  • 누가 그 lock 을 기다리는가
  • owner 가 또 다른 lock 을 기다리고 있는가

이렇게 해야 cycle 이 눈에 보입니다.

3. 코드 경로별 lock order 를 비교한다

synchronized 또는 lock 획득 경로를 검색해서 acquisition 순서를 비교해 보세요.

같은 lock 쌍을 코드 경로마다 다른 순서로 잡고 있다면 root cause 가 상당히 선명해집니다.

4. blocked thread 와 queue growth, worker starvation 을 같이 본다

backlog 가 커지지만 진짜로 stuck 된 thread 가 소수라면, lock cycle 보다 downstream blocking 이나 executor starvation 일 가능성도 큽니다.

5. 충분한 증거를 남긴 뒤에만 restart 한다

restart 는 availability 는 회복할 수 있지만, 실제 원인을 고치는 데 필요한 상태도 같이 없애 버립니다.

정말 restart 해야 한다면, 그 전에 thread 와 lock 상태를 최대한 많이 남겨야 합니다.


예시: 반대 순서의 lock 획득

// path 1
synchronized (a) {
    synchronized (b) {
        update();
    }
}

// path 2
synchronized (b) {
    synchronized (a) {
        update();
    }
}

이 코드는 한동안 아무 문제 없이 돌 수도 있습니다. 그러다가 어느 날 부하 중에 timing 이 맞는 순간 deadlock 이 드러납니다.

그래서 “테스트에서는 괜찮았다” 가 lock-order 버그를 부정하지는 못합니다.


문제를 확인한 뒤 무엇을 바꾸면 좋나

lock order 를 전체 코드에서 하나로 강제한다

고전적인 deadlock 에는 가장 직접적인 fix 입니다.

nested locking 을 줄인다

한 번에 여러 lock 을 잡는 경로가 많다면 ownership 과 critical section 을 단순화해야 합니다.

synchronized 안의 느린 작업을 밖으로 뺀다

root deadlock 이 아니어도 이런 작업은 incident 를 훨씬 크게 만듭니다.

deadlock 과 starvation 패턴을 분리한다

실제 문제가 pool starvation 이나 queue buildup 이라면, monitor cycle 에만 집중하면 해결이 늦어집니다.

진단 장치를 더 둔다

thread dump, blocked thread metric, lock incident playbook 이 있으면 다음 장애 때 훨씬 빨라집니다.


장애 중에 던져볼 질문

이 질문이 꽤 유용합니다.

둘 이상의 thread 가 서로를 기다리는 stable cycle 인가, 아니면 하나의 느리거나 contended path 뒤에 많은 thread 가 쌓인 것뿐인가?

이 구분이 fix 방향을 완전히 바꿉니다.


FAQ

Q. blocked thread 면 다 deadlock 인가

아닙니다. 많은 incident 는 contention 이나 starvation 입니다.

Q. 가장 빠른 첫 단계는 무엇인가

thread dump 를 잡고 같은 lock 주변의 waiting cycle 이 반복되는지 보는 것입니다.

Q. 바로 restart 해야 하나

deadlock, contention, backlog 중 무엇인지 이해할 최소한의 상태를 남긴 뒤가 좋습니다.

Q. deadlock 조사 중에도 CPU 가 높을 수 있나

그렇습니다. 일부 thread 는 여전히 spin, retry, backlog 처리 중일 수 있고, 진짜 deadlocked thread 만 따로 멈춰 있을 수 있습니다.


Sources:

먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.