두 클래스의 초기화 코드가 서로 다른 스레드에서 상대 클래스를 사용하면 왜 교착할 수 있나요?
잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?
답변과 점검 포인트 펼치기답변 접기
구두 답변
정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.
클래스 초기화는 같은 클래스의 <clinit>가 여러 스레드에서 동시에 진행되지 않도록 조정하지만, 서로 다른 클래스의 순환 의존까지 제거하지는 않습니다. T1이 A를 처음 사용해 A 초기화를 시작한 뒤 B의 active use로 들어가고, T2가 동시에 B를 처음 사용해 B 초기화를 시작한 뒤 A를 요구하면, T1은 B 쪽 초기화 완료를 기다리고 T2는 A 쪽 완료를 기다리는 순환 대기가 가능합니다.
final class A {
static int value = B.get();
static int get() { return 1; }
}
final class B {
static int value = A.get();
static int get() { return 2; }
}
단일 스레드에서 먼저 A를 사용하면 A→B 한 방향으로 진행되어 교착이 아닐 수 있고, 재귀적 초기화 경로에서 아직 대입되지 않은 기본값을 관찰할 수도 있습니다. 두 스레드가 각 클래스를 동시에 처음 사용하는 실행에서는 대기 순서가 달라져 교착 가능성이 생깁니다. 그러므로 “정적 초기화는 thread-safe라서 절대 교착하지 않는다”는 답은 직렬화의 범위를 잘못 넓힌 것입니다.
실무에서는 <clinit>에서 외부 락, I/O, 상대 클래스 호출을 줄이고 명시적인 한 방향 bootstrap 단계로 이동합니다. 재현 시 클래스 초기화 시작 시각, active use, thread dump의 대기 대상을 기록해야 하며 소스의 순환만으로 모든 실행이 교착한다고 단정하지 않습니다.
득점 포인트
이해가 드러나는 지점을 확인해 보세요.
- 같은 클래스 초기화의 직렬화와 서로 다른 클래스 사이의 의존 순환을 구분하여 T1의 B 대기와 T2의 A 대기를 상태로 설명합니다.
- 단일 스레드 경로와 두 스레드 동시 첫 접근을 구별하고, 교착 가능성을 실제 접근 순서와 thread dump로 검증해야 한다고 말합니다.
감점 포인트
놓치기 쉬운 조건과 오해를 짚어 보세요.
- static 초기화가 직렬화된다는 이유만으로 모든 클래스 간 교착이 불가능하다고 하면 안 됩니다.
- A와 B의 순환 참조가 있는 소스만 보고 모든 실행에서 교착한다고 단정하면 안 됩니다.
더 파고들 거리
하나 더 질문하며 이해의 범위를 넓혀 보세요.
- 두 스레드가 A와 B를 동시에 처음 호출하도록 만든 테스트에서 thread dump의 대기 그래프를 어떤 정보로 확인할까요?
<clinit>외부 호출을 제거하고 명시적 bootstrap 순서를 두면 실패와 교착 진단 비용이 어떻게 줄어드는지 비교해 보세요.
득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.