동시성 버그는 코드가 틀려서 생기는 게 아니라,
특정 타이밍이 ‘우연히’ 겹칠 때만 모습을 드러난다.

 

1️⃣ 왜 “재현” 문제가 아닌가

일반 버그

  • 입력이 같으면
  • 결과도 같음
  • 재현 가능
  • 디버거로 잡힘

동시성 버그

  • 입력 같아도
  • 스레드 스케줄링 순서가 매번 다름
  • 결과가 달라짐
  • 같은 코드, 같은 데이터인데 결과가 랜덤

👉 이게 핵심입니다.


2️⃣ 동시성 버그의 진짜 원인

JVM/OS 관점

  • 스레드 실행 순서 = OS 스케줄러 결정
  • CPU 코어 상태
  • 컨텍스트 스위칭 타이밍
  • JIT 최적화
  • 캐시 일관성(Memory Model)

👉 개발자가 통제 불가


3️⃣ 그래서 “운”이라는 말이 나온다

동시성 버그는 보통 이렇게 터집니다:

 
Thread A: if (x == null) { Thread B: x = new Object(); Thread A: x.doSomething(); // NPE
  • 이 순서가 딱 이 타이밍에만 발생
  • 로그 찍으면 사라짐
  • 디버거 걸면 안 터짐
  • 서버 늘리면 더 잘 터짐

👉 타이밍 복권


4️⃣ 왜 로그/디버거로 잡기 어려운가

로그 찍으면?

  • I/O 발생
  • 스레드 타이밍 변경
  • 버그 사라짐 (Heisenbug)

디버거 걸면?

  • 스레드 멈춤
  • 실행 순서 강제
  • 재현 실패

👉 관측 자체가 상태를 바꿈


5️⃣ 실무에서 자주 터지는 동시성 버그 유형

① 체크 후 실행 (Check-Then-Act)

 
if (!map.containsKey(k)) { map.put(k, v); }

② 공유 객체에 대한 무보호 접근

  • static 필드
  • 싱글톤 내부 상태
  • 캐시 Map

③ 잘못된 Double-Checked Locking

 
if (instance == null) { synchronized(...) { if (instance == null) { instance = new Instance(); } } }

(volatile 없으면 깨짐)


6️⃣ 그래서 결론이 이렇게 된다

동시성 버그는 “고치기”보다
“처음부터 안 만들기”의 문제다

  • 재현해서 잡는 건 거의 불가능
  • 테스트로도 100% 못 잡음
  • 운 좋으면 안 터질 뿐

7️⃣ 실무자가 취하는 올바른 태도

❌ 잘못된 접근

  • “테스트에서 안 터졌으니 괜찮다”
  • “운영에서 한 번도 안 터졌다”
  • “락 걸면 되겠지”

✅ 올바른 접근

  • 공유 상태 최소화
  • 불변 객체 사용
  • 명시적 동시성 도구 사용
    • ConcurrentHashMap
    • Atomic*
    • synchronized/Lock
  • 설계 단계에서 차단

8️⃣ 이 문장의 진짜 의미 (핵심)

동시성 버그는 실력이 아니라 확률의 문제다.
실력 있는 개발자는 “확률이 0이 되게 설계”한다.


9️⃣ 실무 한 줄 요약

  • 재현 안 된다고 안전한 게 아님
  • 재현되는 동시성 버그는 이미 하급
  • 진짜 위험한 건 “아직 안 터진 것”
LIST

+ Recent posts