Search

멀티 스레드와 동시성 (5) 자바 동기화 메커니즘: 스레드 락 / 대기 집합 / 임계영역

Tags
Study
Java
Last edited time
2025/10/13 00:50
2 more properties
Search
멀티 스레드와 동시성 (5) 자바 동기화 메커니즘: 스레드 락 / 대기 집합 / 임계영역
Study
Java
멀티 스레드와 동시성 (5) 자바 동기화 메커니즘: 스레드 락 / 대기 집합 / 임계영역
Study
Java
멀티스레드 환경에서 공유 자원을 안전하게 다루려면 동기화(synchronization)가 필수입니다.
여기서는 스레드 락, 락 대기 집합(1차), 스레드 대기 집합(2차), 임계영역이라는 네 가지 핵심 개념을 정리합니다.

1. 스레드 락 (Thread Lock)

1.1. 역할

하나의 임계영역에 동시에 단 하나의 스레드만 들어오게 보장
경쟁 조건(race condition) 방지, 불변식 유지

1.2. 특징

메모리 가시성
synchronized / lock()/unlock() 경계는 happens-before 관계를 만들어 임계영역 내 쓰기가 다른 스레드에 보이도록 보장
재진입성(Reentrancy)
동일 스레드가 동일 락을 중첩 획득 가능 (호출 깊이만큼 카운트)

1.3. 종류

1.
모니터락 (synchronized)
synchronized(obj) 구문 사용 → obj 내장 모니터락 획득.
스레드가 못 들어오면 상태는 BLOCKED
2.
명시적 락 (ReentrantLock)
lock()/unlock() 직접 호출.
공정성(fair), tryLock(), lockInterruptibly() 등 옵션 제공.
내부적으로 AQS 큐 + LockSupport.park() 사용 → WAITING/TIMED_WAITING 상태가 흔함

2. 락 대기 집합 (Lock Wait Set, 1차 대기소)

2.1. 역할

락 자체를 얻기 위해 줄 서는 곳.
조건(wait/await)은 고려하지 않고 임계영역 진입만 기다림

2.2. 동작 방식

모니터락 (synchronized)
모니터의 entry set에서 대기
상태: BLOCKED
락이 풀리면 JVM이 후보를 깨워 경쟁
ReentrantLock (AQS)
AQS의 sync queue에서 대기
상태: WAITING/TIMED_WAITING
공정 모드: FIFO 성향 강화, 비공정 모드: 성능 유리

3. 스레드 대기 집합 (Thread Wait Set, 2차 대기소)

3.1. 역할

이미 임계영역에 들어왔으나, 조건이 충족되길 기다리는 곳
이때는 락을 반납하고 기다린다 → 다른 스레드가 조건을 바꿀 기회를 얻음

3.2. 동작 방식

모니터락 (synchronized)
객체마다 단일 wait set
notify() → 임의 스레드 하나 깨움 (생산자→생산자 가능 → 비효율/기아)
notifyAll() → 전부 깨움 (안전하지만 문맥 전환 비용↑)
ReentrantLock + Condition
Condition별 독립 대기 큐 보유
예: notFull(생산자 대기), notEmpty(소비자 대기) → 정확한 상대만 깨워 불필요 경쟁/기아 해소
await()은 락 반납 후 Condition 큐 대기, signal()은 해당 Condition 큐 대기자를 깨워 락 대기 집합(1차) 으로 이동
시간제한, 인터럽트 친화적 메서드 제공

4. 임계영역 (Critical Section)

4.1. 정의

공유 불변식을 지켜야 하는 코드 구간
락으로 보호되며 원자성 확보
예: 0 ≤ size ≤ capacity

4.2. 권장사항

1.
최소화: 짧고 단순하게 작성 (락 보유 시간↓)
2.
외부 호출 금지: I/O, 네트워크 호출은 락 밖에서
3.
Sleep 금지: Thread.sleep()은 락을 반납하지 않아 교착 유발
4.
조건 사용 규율: while 재검증 필수
5.
가시성 확보: 락 경계가 happens-before를 보장

5. 락 대기 vs 스레드 대기 비교

구분
언제 들어가는가
상태
예시
락 대기 집합 (1차)
임계영역에 진입하려다 락을 못 얻었을 때
BLOCKED (synchronized) WAITING (ReentrantLock)
- 모니터 entry set - AQS sync queue
스레드 대기 집합 (2차)
임계영역에 들어왔지만 조건이 불만족일 때
WAITING / TIMED_WAITING
- 모니터 wait set - Condition 큐

6. 흐름(상태 전이)

1.
스레드 → 임계영역 진입 시도
성공 → 임계영역 실행
실패 → 락 대기 집합(1차)
2.
임계영역에서 조건 불만족 → wait()/await() 호출
락 반납 + 스레드 대기 집합(2차) 이동
3.
notify/signal 수신 → 2차 → 1차 이동
4.
락 획득 성공 → 조건 재검증(while) 후 실행
5.
작업 종료 → 락 해제
flowchart LR
  classDef area fill:#fff,stroke:#333,rx:8,ry:8
  classDef dashed fill:#fff,stroke:#c33,stroke-dasharray:6 4,rx:8,ry:8,color:#c33
  classDef wait fill:#eef,stroke:#55a,rx:10,ry:10
  classDef lock fill:#fff,stroke:#333,rx:8,ry:8

  subgraph System["공유 객체/구조"]
    direction TB
    L["스레드 락"]:::lock
    subgraph LQ["락 대기 집합 (1차)</br>락 획득 대기"]
      BQ["BLOCKED / WAITING"]
    end

    subgraph CS["임계영역"]
      direction TB
      R["공유 상태/큐"]
      subgraph WQ["스레드 대기 집합 (2차)</br>조건 대기"]
        W1["WAITING / TIMED_WAITING"]
      end
    end
  end

  T["실행 스레드"] -->|락 시도| L
  L -->|성공| CS
  L -->|실패| LQ
  CS -->|조건 false → wait/await| WQ
  WQ -->|notify/signal| LQ
  LQ -->|락 재획득| CS
  CS -->|unlock| L
Mermaid
복사
flowchart LR
  %% 좌우 여백
  classDef box fill:#fff,stroke:#333,rx:6,ry:6
  classDef area fill:#fff,stroke:#333,stroke-width:1.5px,rx:8,ry:8
  classDef dashed fill:#fff,stroke:#c33,stroke-dasharray: 6 4,rx:8,ry:8,color:#c33
  classDef wait fill:#eef,stroke:#88f,rx:12,ry:12,color:#000
  classDef ext fill:#ffbf00,stroke:#c90,rx:16,ry:16,color:#000

  %% 외곽 컨테이너 (BoundedQueue)
  subgraph BQ[BoundedQueue]
  direction LR

    %% 왼쪽/오른쪽 API
    TAKE[[take]]:::box
    PUT[[put]]:::box

    %% 가운데 스택 배치용 컬럼
    subgraph MID[x001]
    direction TB

      %% lock, 락 대기 집합
      LOCK([🔒 lock]):::box
      LWAIT[락 대기 집합]:::area

      %% 임계영역 테두리(점선)
      subgraph CRIT[임계 영역]
      direction TB
      class CRIT dashed

        %% 큐 슬롯 2개
        subgraph Q[queue]
        direction LR
          S1([1]):::box
          S2([2]):::box
        end

        %% 스레드 대기 집합 (단일 wait set)
        subgraph WSET[스레드 대기 집합]
        direction LR
          C1((c1<br>WAITING)):::wait
          C2((c2<br>WAITING)):::wait
          C3((c3<br>WAITING)):::wait
        end
      end
    end
  end

  %% 외부 생산자들
  P1((p1<br>data1)):::ext
  P2((p2<br>data2)):::ext
  P3((p3<br>data3)):::ext

  %% 배선
  P1 --> PUT
  P2 --> PUT
  P3 --> PUT
  TAKE --> BQ
Mermaid
복사

7. 단계별 비교 요약

단계
스레드 락
1차 대기소
2차 대기소
비고
동기화 없음
레이스 조건 발생
synchronized + sleep()
모니터락
BLOCKED
Sleep은 락 반납 안 함 → 교착 위험
synchronized + wait/notify
모니터락
BLOCKED
wait set (단일)
올바른 락 반납·조건 대기 가능
ReentrantLock + Condition
명시적 락
AQS 큐 (WAITING)
여러 Condition 큐
생산자/소비자 분리 가능
BlockingQueue
내부 ReentrantLock
내부
내부
모든 메커니즘을 캡슐화

8. 자주 헷갈리는 포인트

BLOCKED vs WAITING
BLOCKED: 락 획득 대기(모니터 entry set)
WAITING: 조건 대기(wait set, Condition 큐)
미싱 시그널
while-await 패턴을 지키면 안전
ReentrantLock ≠ 모니터락
구현 기반(AQS) 다름, 대신 Condition 분리·tryLock 등 제어력 제공

9. 결론

락 대기 집합(1차) = “락 문 앞 대기열”
스레드 대기 집합(2차) = “조건 기다림 대기실”
언제 무엇을 쓰나?
가장 권장: BlockingQueue 같은 동시성 컬렉션(내부에 ReentrantLock/Condition 포함)
직접 제어 필요: 조건 큐 분리·정밀한 대기 정책이 필요하면 ReentrantLock + Condition
단순·소규모: synchronized + wait/notify 가능(단, 단일 wait set의 한계를 이해하고 사용할 것)