Unit 2.2 — synchronized

Psj·5일 전

F-lab

목록 보기
160/197

Unit 2.2 — synchronized: 둘 다 해결, 대신 느림

F-LAB JAVA · 5주차 · Phase 2 · 동시성 안전 도구 3종 비교


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • synchronized 가 가시성을 보장 하는 원리는?
  • synchronized 가 원자성을 보장 하는 원리는?
  • 성능 비용 (BLOCKED) 의 의미는?
  • "가장 안전하지만 가장 비싼 도구" 인 이유는?
  • synchronized 의 "전체 락" 단점 은?
  • 읽기만 하는데도 synchronized 가 필요한 경우는?
  • 메서드 락 vs 블록 락 의 차이는?
  • 모니터 (monitor) 와 락 의 관계는?
  • synchronized 의 적절한 사용 은?

🎯 핵심 한 문장

synchronized 는 한 번에 한 스레드만 임계 영역에 진입시켜 원자성을 보장하고, 락 획득·해제 시점에 메인 메모리와 동기화하여 가시성도 보장하는, 가장 안전하지만 다른 스레드를 BLOCKED 시켜 가장 비싼 도구다.
원자성 보장 — synchronized 블록/메서드는 모니터 락 (monitor lock) 을 통해 한 번에 한 스레드만 진입시키므로, 복합 연산 (count++ 등) 이 다른 스레드의 개입 없이 완료된다.
가시성 보장 — 락을 획득할 때 캐시를 무효화하고 메인 메모리에서 읽으며, 락을 해제할 때 변경 사항을 메인 메모리에 반영하므로 다른 스레드가 최신 값을 본다.
성능 비용 — 락을 잡은 스레드가 임계 영역을 실행하는 동안 다른 스레드는 BLOCKED 상태로 대기해야 하므로 병렬성이 떨어지고, 컨텍스트 스위칭·락 경쟁 비용이 발생한다.
또한 synchronized 는 전체 락 (객체/메서드 단위) 이라 세밀하지 못하고, 읽기만 하는 경우에도 다른 스레드가 쓰는 중이면 가시성·일관성을 위해 동기화가 필요하다.

비유 — 1인용 화장실

synchronized = 1인용 화장실:

원자성 (한 명만):
  - 한 번에 한 명만 입장
  - 문 잠금 (락)
  - 안에서 볼일 (임계 영역)
  - 끝나면 다음 사람

가시성 (상태 동기화):
  - 들어갈 때 현재 상태 확인 (메인 메모리 읽기)
  - 나올 때 상태 반영 (메인 메모리 쓰기)
  - 다음 사람이 최신 상태 봄

성능 비용 (대기):
  - 한 명 쓰는 동안
  - 나머지 줄 서서 대기 (BLOCKED)
  - 병렬 X (한 명씩)

전체 락 단점:
  - 화장실 전체 잠금
  - 세면대만 쓰고 싶어도 못 씀
  - 너무 큰 락

→ 가장 안전 (충돌 X)
→ 가장 느림 (한 명씩)

→ synchronized = 모니터 락 (한 명씩), 원자성 + 가시성 보장, 대신 BLOCKED (느림).


🧭 9개 섹션 로드맵

1. 원자성 보장 원리
2. 가시성 보장 원리
3. 성능 비용 (BLOCKED)
4. 가장 안전, 가장 비쌈
5. 전체 락 단점
6. 읽기에도 필요한 경우
7. 메서드 락 vs 블록 락
8. 모니터와 락
9. 면접 + 자기 점검

1️⃣ 원자성 보장 원리

1.1 한 스레드만 진입

원자성 보장:

  synchronized:
    - 한 번에 한 스레드만 진입
    - 모니터 락
    - 다른 스레드 대기

  → 복합 연산도 안전

1.2 임계 영역

임계 영역 (Critical Section):

  synchronized 블록/메서드:
    - 한 스레드만 실행
    - 다른 스레드 대기 (BLOCKED)

  → 원자적 실행 (중간 개입 X)

1.3 count++ 안전

// synchronized 로 count++ 안전
private int count = 0;

public synchronized void increment() {
    count++;   // read-modify-write 가 원자적
    // 한 스레드만 진입 → 손실 X
}

1.4 복합 연산

// 여러 연산도 원자적
private int balance = 0;
private final List<String> log = new ArrayList<>();

public synchronized void transfer(int amount) {
    balance += amount;            // 1
    log.add("transfer: " + amount);  // 2
    // 1, 2 가 함께 원자적 (한 스레드)
}

1.5 ILIC 의 맥락

@Service
public class SynchronizedAtomicity {
    
    private int processedCount = 0;
    private BigDecimal totalFreight = BigDecimal.ZERO;
    
    // 복합 연산 원자적
    public synchronized void recordProcessing(Shipment shipment) {
        processedCount++;                              // 1
        totalFreight = totalFreight.add(shipment.getWeight());  // 2
        // 1, 2 함께 (한 스레드만)
        // 다른 스레드 개입 X
    }
    
    public synchronized int getCount() {
        return processedCount;
    }
}

1.6 자기 점검 답변

synchronized가 원자성을 보장하는 원리는?

:
1. 원리:

  • 한 스레드만 진입
  • 모니터 락
  1. 임계 영역:

    • 다른 스레드 대기
  2. count++:

    • RMW 원자적
  3. 복합 연산:

    • 함께 원자적

2️⃣ 가시성 보장 원리

2.1 메모리 동기화

가시성 보장:

  synchronized:
    - 락 획득 시: 캐시 무효화 → 메인 메모리 읽기
    - 락 해제 시: 변경 → 메인 메모리 반영

  → 다른 스레드 최신 값

2.2 락 획득 시

락 획득 시:

  - 캐시 무효화
  - 메인 메모리에서 읽기
  - 최신 값 보장

→ 들어갈 때 최신 상태

2.3 락 해제 시

락 해제 시:

  - 변경 사항 메인 메모리에 쓰기
  - 다른 스레드가 볼 수 있게

→ 나올 때 변경 반영

2.4 happens-before

happens-before:

  락 해제 happens-before 락 획득:
    - 이전 스레드 변경
    - 다음 스레드에 보임

  → 가시성 보장 (JMM)

2.5 ILIC 의 맥락

@Service
public class SynchronizedVisibility {
    
    private boolean ready = false;
    private Shipment data;
    
    // 쓰기 (락 해제 시 메인 메모리 반영)
    public synchronized void prepare(Shipment shipment) {
        data = shipment;   // 변경
        ready = true;      // 변경
        // 락 해제 → 메인 메모리 (가시성)
    }
    
    // 읽기 (락 획득 시 메인 메모리 읽기)
    public synchronized Shipment getData() {
        // 락 획득 → 최신 값 읽기 (가시성)
        if (ready) {
            return data;   // 최신
        }
        return null;
    }
}

2.6 자기 점검 답변

synchronized가 가시성을 보장하는 원리는?

:
1. 메모리 동기화:

  • 획득/해제 시
  1. 획득 시:

    • 캐시 무효화
    • 메인 메모리 읽기
  2. 해제 시:

    • 변경 반영
  3. happens-before:

    • 해제 → 획득

3️⃣ 성능 비용 (BLOCKED)

3.1 BLOCKED 대기

성능 비용 — BLOCKED:

  락 잡은 스레드 실행 중:
    - 다른 스레드 BLOCKED
    - 락 대기
    - 병렬 X

3.2 병렬성 저하

병렬성 저하:

  임계 영역:
    - 한 스레드만
    - 나머지 대기

  → 직렬화 (순차)
  → 병렬 이점 ↓

3.3 비용 요소

synchronized 비용:

1. 락 획득/해제
   - CAS 또는 시스템 콜

2. BLOCKED 대기
   - 컨텍스트 스위칭

3. 경쟁 (contention)
   - 여러 스레드 대기

4. 직렬화
   - 병렬성 ↓

3.4 경쟁의 영향

경쟁 영향:

  경쟁 낮음:
    - 락 빨리 획득
    - 비용 적음

  경쟁 높음:
    - 많은 스레드 대기
    - 스위칭 폭증
    - 비용 ↑

3.5 ILIC 의 맥락

@Service
public class SynchronizedCost {
    
    private int count = 0;
    
    // ❌ 큰 임계 영역 (병렬성 ↓)
    public synchronized void wrongHeavy(Shipment shipment) {
        count++;
        heavyComputation(shipment);   // 무거운 작업도 락 안에서
        // 다른 스레드 오래 BLOCKED
    }
    
    // ✓ 작은 임계 영역 (병렬성 ↑)
    public void betterMinimal(Shipment shipment) {
        BigDecimal result = heavyComputation(shipment);   // 락 밖
        synchronized (this) {
            count++;   // 락 안 (최소)
        }
        // 무거운 작업은 병렬, 카운터만 동기화
    }
    
    private BigDecimal heavyComputation(Shipment s) { return s.getWeight(); }
}

3.6 자기 점검 답변

성능 비용 (BLOCKED) 의 의미는?

:
1. BLOCKED:

  • 다른 스레드 대기
  • 병렬 X
  1. 병렬성 저하:

    • 직렬화
  2. 비용:

    • 락, BLOCKED, 경쟁
  3. 경쟁:

    • 높으면 비용 ↑

4️⃣ 가장 안전, 가장 비쌈

4.1 가장 안전

가장 안전:

  synchronized:
    - 가시성 ✅
    - 원자성 ✅
    - 복합 연산 OK
    - 여러 변수 일관성

  → 확실한 안전

4.2 가장 비쌈

가장 비쌈:

  synchronized:
    - BLOCKED (대기)
    - 병렬성 ↓
    - 락 경쟁

  → 성능 비용 큼

4.3 트레이드오프

안전 vs 성능 트레이드오프:

  synchronized:
    + 가장 안전
    - 가장 느림

  volatile:
    + 빠름
    - 가시성만

  Atomic:
    + 빠름 (CAS)
    - 단일 변수

4.4 언제 선택

synchronized 선택:

  - 복잡한 복합 연산
  - 여러 변수 일관성
  - 정확성 > 성능
  - 단순함 우선

→ 안전이 최우선일 때

4.5 최적화

synchronized 최적화:

  - 임계 영역 최소화
  - 락 분할 (여러 락)
  - 읽기 많으면 ReadWriteLock
  - 단일 변수면 Atomic

4.6 ILIC 의 맥락

@Service
public class SafestButSlowest {
    
    // 복잡한 일관성 → synchronized (안전 우선)
    private final Map<Long, Shipment> shipments = new HashMap<>();
    private BigDecimal totalWeight = BigDecimal.ZERO;
    private int count = 0;
    
    public synchronized void addShipment(Shipment shipment) {
        // 여러 상태 일관성 (synchronized 가 적합)
        shipments.put(shipment.getId(), shipment);
        totalWeight = totalWeight.add(shipment.getWeight());
        count++;
        // 세 변수 일관성 (한 스레드만)
    }
    
    public synchronized Summary getSummary() {
        return new Summary(count, totalWeight);
        // 일관된 스냅샷
    }
    
    record Summary(int count, BigDecimal totalWeight) {}
}

4.7 자기 점검 답변

"가장 안전하지만 가장 비싼 도구" 인 이유는?

:
1. 가장 안전:

  • 가시성 + 원자성
  • 복합/여러 변수
  1. 가장 비쌈:

    • BLOCKED
    • 병렬성 ↓
  2. 트레이드오프:

    • 안전 vs 성능
  3. 선택:

    • 정확성 우선

5️⃣ 전체 락 단점

5.1 전체 락

전체 락:

  synchronized 메서드/객체:
    - 객체 전체 락
    - 세밀하지 못함
    - 관련 없는 작업도 대기

5.2 세밀하지 못함

// 전체 락 문제
public class Account {
    private int balance;
    private String name;
    
    public synchronized void deposit(int amount) {
        balance += amount;   // balance 만 관련
    }
    
    public synchronized void setName(String name) {
        this.name = name;   // name 만 관련
    }
    // deposit 과 setName 이 같은 락
    // → 동시 실행 불가 (불필요한 대기)
}

5.3 락 분할

// 락 분할 (세밀하게)
public class Account {
    private int balance;
    private String name;
    private final Object balanceLock = new Object();
    private final Object nameLock = new Object();
    
    public void deposit(int amount) {
        synchronized (balanceLock) {   // balance 락
            balance += amount;
        }
    }
    
    public void setName(String name) {
        synchronized (nameLock) {   // name 락 (별도)
            this.name = name;
        }
    }
    // deposit 과 setName 동시 가능
}

5.4 동시성 컬렉션 비교

전체 락 vs 분할:

Hashtable (전체 락):
  - 모든 메서드 synchronized
  - 동시성 ↓

ConcurrentHashMap (분할):
  - 버킷/세그먼트 단위
  - 동시성 ↑

→ 분할이 효율적

5.5 ILIC 의 맥락

@Service
public class FullLockDrawback {
    
    // ❌ 전체 락 (세밀하지 못함)
    public class ShipmentManagerBad {
        private int bookingCount;
        private int shipmentCount;
        
        public synchronized void addBooking() { bookingCount++; }
        public synchronized void addShipment() { shipmentCount++; }
        // 둘이 같은 락 → 동시 불가
    }
    
    // ✓ 락 분할 또는 Atomic
    public class ShipmentManagerGood {
        private final AtomicInteger bookingCount = new AtomicInteger();
        private final AtomicInteger shipmentCount = new AtomicInteger();
        
        public void addBooking() { bookingCount.incrementAndGet(); }
        public void addShipment() { shipmentCount.incrementAndGet(); }
        // 독립 (동시 가능)
    }
}

5.6 자기 점검 답변

synchronized의 "전체 락" 단점은?

:
1. 전체 락:

  • 객체 전체
  • 세밀하지 못함
  1. 문제:

    • 관련 없는 작업도 대기
  2. 해결:

    • 락 분할
    • 별도 락 객체
  3. 비교:

    • Hashtable vs ConcurrentHashMap

6️⃣ 읽기에도 필요한 경우

6.1 읽기 동기화

읽기에도 synchronized 필요:

  다른 스레드가 쓰는 중:
    - 읽기도 동기화 필요
    - 가시성 (최신 값)
    - 일관성 (중간 상태 방지)

6.2 가시성

// 읽기 동기화 (가시성)
private int value = 0;

public synchronized void write(int v) {
    value = v;
}

public synchronized int read() {   // 읽기도 synchronized
    return value;
    // 동기화 없으면 옛 값 (가시성)
}

6.3 일관성

// 일관성 (중간 상태 방지)
private int x, y;

public synchronized void update(int newX, int newY) {
    x = newX;   // 중간 상태 (x 변경, y 안 됨)
    y = newY;
}

public synchronized Point read() {   // 읽기 동기화
    return new Point(x, y);
    // 동기화 없으면 중간 상태 (x 새, y 옛)
}

6.4 양쪽 동기화 필요

양쪽 동기화:

  쓰기만 synchronized:
    - 읽기가 중간/옛 값

  읽기도 synchronized:
    - 일관된 최신 값

→ 읽기/쓰기 모두 동기화

6.5 ILIC 의 맥락

@Service
public class ReadSynchronization {
    
    private int totalShipments = 0;
    private BigDecimal totalWeight = BigDecimal.ZERO;
    
    // 쓰기
    public synchronized void add(Shipment shipment) {
        totalShipments++;
        totalWeight = totalWeight.add(shipment.getWeight());
    }
    
    // 읽기도 synchronized (일관성 + 가시성)
    public synchronized Stats getStats() {
        return new Stats(totalShipments, totalWeight);
        // 동기화 없으면:
        // - 옛 값 (가시성)
        // - 중간 상태 (count 새, weight 옛)
    }
    
    record Stats(int count, BigDecimal weight) {}
}

6.6 자기 점검 답변

읽기만 하는데도 synchronized가 필요한 경우는?

:
1. 필요:

  • 다른 스레드 쓰는 중
  • 가시성 + 일관성
  1. 가시성:

    • 최신 값
  2. 일관성:

    • 중간 상태 방지
  3. 양쪽:

    • 읽기/쓰기 모두

7️⃣ 메서드 락 vs 블록 락

7.1 메서드 락

// 메서드 락 (메서드 전체)
public synchronized void method() {
    // 전체가 임계 영역
}
// 락 객체: this (인스턴스 메서드)
// 락 객체: 클래스 (static 메서드)

7.2 블록 락

// 블록 락 (일부만)
public void method() {
    // 락 밖 (병렬)
    doSomething();
    
    synchronized (lock) {
        // 락 안 (임계 영역, 최소)
        criticalSection();
    }
    
    // 락 밖
    doMore();
}

7.3 비교

항목메서드 락블록 락
범위메서드 전체일부
락 객체this/클래스지정
세밀함낮음높음
성능낮음높음

7.4 블록 락 권장

블록 락 권장:

  임계 영역 최소화:
    - 락 안 최소
    - 락 밖 병렬
    - 성능 ↑

  락 객체 지정:
    - 세밀한 제어

7.5 락 객체

// 락 객체 선택
private final Object lock = new Object();   // 전용 락

public void method() {
    synchronized (lock) {   // 전용 락 객체
        // ...
    }
}
// this 대신 전용 객체 (외부 간섭 방지)

7.6 ILIC 의 맥락

@Service
public class MethodVsBlockLock {
    
    private final Object countLock = new Object();
    private int count = 0;
    
    // ❌ 메서드 락 (전체)
    public synchronized void processWrong(Shipment shipment) {
        validate(shipment);      // 무거움 (락 불필요)
        calculate(shipment);     // 무거움 (락 불필요)
        count++;                 // 락 필요
        // 전체 락 → 병렬성 ↓
    }
    
    // ✓ 블록 락 (최소)
    public void processBetter(Shipment shipment) {
        validate(shipment);      // 락 밖 (병렬)
        calculate(shipment);     // 락 밖 (병렬)
        synchronized (countLock) {
            count++;             // 락 안 (최소)
        }
    }
    
    private void validate(Shipment s) { }
    private void calculate(Shipment s) { }
}

7.7 자기 점검 답변

메서드 락 vs 블록 락의 차이는?

:
1. 메서드 락:

  • 메서드 전체
  • this/클래스
  1. 블록 락:

    • 일부 (최소)
    • 락 객체 지정
  2. 권장:

    • 블록 락 (최소화)
  3. 락 객체:

    • 전용 객체

8️⃣ 모니터와 락

8.1 모니터

모니터 (Monitor):

  각 객체가 가진 동기화 메커니즘.
  - 모니터 락 (intrinsic lock)
  - synchronized 가 사용

8.2 모니터 락

모니터 락:

  모든 자바 객체:
    - 모니터 락 보유
    - synchronized 가 획득/해제

  한 스레드만 보유:
    - 다른 스레드 대기

8.3 재진입

// 재진입 (reentrant)
public synchronized void a() {
    b();   // 같은 락 재획득 (재진입 OK)
}

public synchronized void b() {
    // a 가 잡은 락, b 도 같은 락
    // 재진입 가능 (데드락 X)
}
// 자바 모니터 락 = 재진입 가능

8.4 wait/notify

모니터와 wait/notify:

  wait/notify 도 모니터:
    - synchronized 안에서만
    - 모니터 락 + 대기 집합

  (4주차 Phase 6)

8.5 ILIC 의 맥락

@Service
public class MonitorAndLock {
    
    // 모니터 락 (this)
    public synchronized void method1() {
        // this 의 모니터 락
    }
    
    // 재진입
    public synchronized void outer(Shipment shipment) {
        inner(shipment);   // 같은 락 재진입
    }
    
    public synchronized void inner(Shipment shipment) {
        // outer 가 잡은 락, inner 재진입 OK
        process(shipment);
    }
    
    // 전용 락 객체의 모니터
    private final Object lock = new Object();
    public void withDedicatedLock() {
        synchronized (lock) {   // lock 객체의 모니터
            // ...
        }
    }
    
    private void process(Shipment s) { }
}

8.6 자기 점검 답변

모니터와 락의 관계는?

:
1. 모니터:

  • 객체 동기화 메커니즘
  • 모니터 락
  1. 모니터 락:

    • 모든 객체 보유
    • 한 스레드만
  2. 재진입:

    • 같은 락 재획득
  3. wait/notify:

    • 모니터 기반

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
원자성 보장?한 스레드만 (모니터 락)
가시성 보장?획득/해제 시 동기화
성능 비용?BLOCKED (병렬 X)
가장 안전/비쌈?둘 다 보장, 느림
전체 락 단점?세밀하지 못함
읽기 동기화?가시성/일관성
메서드 vs 블록?전체 vs 최소
모니터?객체 동기화
재진입?같은 락 재획득
최적화?임계 영역 최소

9.2 자기 점검 체크리스트

원자성

  • 한 스레드만
  • 모니터 락

가시성

  • 획득/해제 동기화

비용

  • BLOCKED

안전/비쌈

  • 트레이드오프

전체 락

  • 단점
  • 분할

읽기

  • 동기화 필요

메서드/블록

  • 차이

모니터

  • 락, 재진입

9.3 추가 심화 질문

Q1: synchronized 성능 개선 (JVM)?

답:

  • 바이어스 락 (biased locking)
  • 경량 락 (lightweight)
  • 중량 락 (heavyweight)
  • JVM 이 경쟁에 따라 승급

Q2: synchronized vs ReentrantLock?

답:

  • synchronized: 간편, 자동 해제
  • ReentrantLock: tryLock, 공정성, 인터럽트
  • 기능 많으면 Lock
  • 단순하면 synchronized

Q3: 클래스 락 vs 인스턴스 락?

답:

  • 인스턴스 메서드: this 락
  • static 메서드: 클래스 락 (Class 객체)
  • 서로 다른 락
  • 동시 가능

Q4: synchronized 와 재정렬?

답:

  • 임계 영역 내 재정렬 가능
  • 단, 경계 밖으로 안 나감
  • happens-before 보장
  • 가시성/순서 OK

Q5: 락 경쟁 줄이기?

답:

  • 임계 영역 최소화
  • 락 분할
  • 락 스트라이핑
  • Atomic/불변 객체

🎯 핵심 요약 — 3줄 정리

1. 둘 다 보장

  • 원자성: 한 스레드만 (모니터 락)
  • 가시성: 획득/해제 시 메모리 동기화

2. 성능 비용

  • BLOCKED (다른 스레드 대기)
  • 가장 안전, 가장 비쌈

3. 주의

  • 전체 락 (세밀하지 못함 → 분할)
  • 읽기도 동기화 필요 (가시성/일관성)
  • 임계 영역 최소화

📚 다음으로...

Unit 2.3 — volatile: 가시성만, 원자성은 ❌

이번 Unit에서 synchronized 를 봤다면, 다음은 volatile (가시성만).

  • 가시성 보장 (메인 메모리)
  • 원자성 X (count++ 위험)
  • 적합한 시나리오 (플래그)

Phase 2 진행 상황

🚀 Phase 2 — 동시성 안전 도구 3종 비교
  ✅ Unit 2.1 두 가지 동시성 문제 구분
  ✅ Unit 2.2 synchronized ← 여기
  ⏭ Unit 2.3 volatile
  ⏭ Unit 2.4 Atomic + CAS (★ 마스터)

5주차 누적 진행

✅ Phase 1 — 스레드 풀 필요성 (3 Unit)
🚀 Phase 2 — 동시성 안전 도구 (2/4 진행)

총: 5/26 Unit
profile
Software Developer

0개의 댓글