4주차 Unit 5.1 — synchronized의 한계 정리

Psj·2026년 5월 21일

F-lab

목록 보기
137/237

Unit 5.1 — synchronized의 한계 정리

F-LAB JAVA · 4주차 · Phase 5 · 정교한 락: LockSupport와 ReentrantLock
🚀 Phase 5 시작 — 정교한 락 진입


📌 학습 목표

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

  • synchronized 의 핵심 한계 3가지 는?
  • 무한 대기 (타임아웃 불가) 의 문제는?
  • 인터럽트 불가 의 문제는?
  • 공정성 보장 X 의 문제는?
  • 운영 환경에서 무한 대기 가 어떤 사고로 이어지나?
  • 데드락 상황에서 synchronized 가 회복 불가 한 이유는?
  • synchronized 의 장점 (한계에도 불구하고) 은?
  • 더 정교한 락 도구 (Lock 인터페이스) 의 등장 배경은?
  • 언제 synchronized, 언제 Lock 인가?

🎯 핵심 한 문장

synchronized 는 간단하고 자동 반환되는 장점이 있지만, (1) 무한 대기 (타임아웃 불가), (2) 인터럽트 불가, (3) 공정성 보장 X 라는 세 가지 핵심 한계를 가진다.
무한 대기 — 락을 획득할 때까지 무한정 BLOCKED 되며, "1초만 기다리고 포기" 같은 타임아웃을 설정할 수 없어, 락이 영원히 안 풀리면 영원히 대기한다.
인터럽트 불가 — synchronized 대기 중인 스레드는 interrupt() 로 깨울 수 없어, 응답성이 중요한 작업에서 문제가 된다.
공정성 보장 X — 락 획득 순서가 정의되지 않아 (비공정), 특정 스레드가 계속 락을 못 받는 기아 (starvation) 가 발생할 수 있다.
이러한 한계로 인해 데드락 발생 시 synchronized 로는 회복할 방법이 없으며, 자바는 이를 극복하는 정교한 락 도구 (Lock 인터페이스, ReentrantLock) 를 제공한다 (이번 Phase 의 주제).

비유 — 화장실 1개의 한계

synchronized = 단순한 화장실 (장점):
  - 들어가면 자동으로 잠김
  - 나오면 자동으로 풀림 (자동 반환)
  - 단순함

한계 1 — 무한 대기:
  - 안의 사람이 안 나오면
  - 영원히 기다림 (포기 불가)
  - "5분만 기다리고 다른 곳으로" 불가

한계 2 — 인터럽트 불가:
  - 밖에서 문을 두드려도 (interrupt)
  - 기다리는 사람은 안 나옴
  - 급한 일 있어도 못 빠짐

한계 3 — 공정성 X:
  - 줄 서도 순서 보장 X
  - 새치기 가능
  - 운 나쁘면 영원히 못 들어감 (기아)

ReentrantLock = 정교한 화장실 (Phase 5):
  - 타임아웃 (tryLock)
  - 두드리면 나옴 (lockInterruptibly)
  - 줄 순서 (공정 모드)

→ synchronized 는 단순하지만 3가지 한계, ReentrantLock 이 극복.


🧭 9개 섹션 로드맵

1. synchronized의 장점 (먼저)
2. 한계 1 — 무한 대기
3. 한계 2 — 인터럽트 불가
4. 한계 3 — 공정성 X
5. 운영 환경의 무한 대기 사고
6. 데드락과 회복 불가
7. 더 정교한 락의 필요성
8. synchronized vs Lock 선택
9. 면접 + 자기 점검

1️⃣ synchronized의 장점 (먼저)

1.1 한계 전에 장점

synchronized 의 장점:

  한계만 보기 전에 장점 인정.

1. 간단함
   - 키워드만
   - 학습 쉬움

2. 자동 반환
   - unlock 불필요
   - 예외 시에도 안전

3. 재진입
   - 같은 스레드 재획득

4. 가독성
   - 명확한 의도

1.2 간단함

// synchronized — 매우 간단
public synchronized void method() {
    // 동기화됨
}

// 또는 블록
synchronized (lock) {
    // 동기화됨
}

// 한 키워드로 동기화

1.3 자동 반환

// synchronized — 자동 반환 (예외 안전)
synchronized (lock) {
    doWork();
    throw new RuntimeException();   // 예외!
    // 그래도 락 자동 반환
}

// vs ReentrantLock — 수동
lock.lock();
try {
    doWork();
} finally {
    lock.unlock();   // 명시적 (깜박하면 데드락)
}

1.4 적합한 경우

synchronized 가 적합:

  - 단순한 동기화
  - 짧은 임계 영역
  - 타임아웃/인터럽트 불필요
  - 공정성 불필요

대부분의 단순한 경우 충분

1.5 ILIC 의 맥락

@Service
public class SimpleSyncService {
    
    private int counter = 0;
    
    // synchronized — 단순, 충분
    public synchronized void increment() {
        counter++;
        // 짧고 단순
        // 타임아웃/인터럽트 불필요
        // synchronized 적합
    }
    
    // 자동 반환 (예외 안전)
    public synchronized void process(Shipment shipment) {
        counter++;
        validate(shipment);   // 예외 가능, 락 자동 반환
    }
    
    private void validate(Shipment s) { }
}

1.6 자기 점검 답변

synchronized의 장점은?

:
1. 간단함:

  • 키워드만
  • 학습 쉬움
  1. 자동 반환:

    • unlock 불필요
    • 예외 안전
  2. 재진입:

    • 같은 스레드
  3. 적합:

    • 단순, 짧은 임계 영역

2️⃣ 한계 1 — 무한 대기

2.1 무한 대기

무한 대기 (타임아웃 불가):

  synchronized 진입 시 락 없으면
  무한정 BLOCKED.

문제:
  - 타임아웃 설정 불가
  - "N초만 기다리고 포기" 불가
  - 락 영원히 안 풀리면 영원히 대기

2.2 코드

// synchronized — 무한 대기
public void process() {
    synchronized (lock) {   // 락 없으면 무한 BLOCKED
        // 락 받을 때까지 영원히
        doWork();
    }
}

// 타임아웃 불가
// "5초만 시도하고 다른 일" 불가

2.3 문제 시나리오

무한 대기 문제:

  스레드 A 가 락 보유 후 멈춤:
    - 무한 루프
    - 데드락
    - 외부 자원 무한 대기

  스레드 B (synchronized 대기):
    - A 가 안 풀면 영원히 BLOCKED
    - 포기 불가
    - 응답 없음

2.4 ReentrantLock 의 해결 (예고)

// ReentrantLock — 타임아웃 가능
ReentrantLock lock = new ReentrantLock();

public boolean processWithTimeout() throws InterruptedException {
    if (lock.tryLock(5, TimeUnit.SECONDS)) {   // 5초만
        try {
            doWork();
            return true;
        } finally {
            lock.unlock();
        }
    }
    // 5초 내 못 얻으면
    log.warn("Could not acquire lock");
    return false;   // 포기 (무한 대기 X)
}

2.5 시각화

무한 대기:

synchronized:
  스레드 A: [락 보유, 멈춤────────────────]
  스레드 B: [BLOCKED 영원히────────────────]
                  ↑ 포기 불가

ReentrantLock (tryLock):
  스레드 A: [락 보유, 멈춤────]
  스레드 B: [tryLock 5초][포기→다른 일]
                       ↑ 타임아웃

2.6 ILIC 의 맥락

@Service
public class InfiniteWaitProblem {
    
    private final Object lock = new Object();
    
    // ❌ synchronized — 무한 대기
    public void processSync(Shipment shipment) {
        synchronized (lock) {
            // 다른 스레드가 락 안 풀면 영원히 대기
            callSlowExternalApi(shipment);   // 만약 멈추면?
        }
    }
    
    // ✓ ReentrantLock — 타임아웃
    private final ReentrantLock reentrantLock = new ReentrantLock();
    
    public boolean processWithTimeout(Shipment shipment) {
        try {
            if (reentrantLock.tryLock(10, TimeUnit.SECONDS)) {
                try {
                    callSlowExternalApi(shipment);
                    return true;
                } finally {
                    reentrantLock.unlock();
                }
            }
            log.warn("Timeout for shipment {}", shipment.getId());
            return false;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    private void callSlowExternalApi(Shipment s) { }
}

2.7 자기 점검 답변

무한 대기의 문제는?

:
1. 무한 대기:

  • 타임아웃 불가
  • 락까지 무한 BLOCKED
  1. 문제:

    • 락 안 풀리면 영원히
    • 포기 불가
  2. 시나리오:

    • 보유 스레드 멈춤
    • 데드락
  3. 해결:

    • ReentrantLock.tryLock(timeout)

3️⃣ 한계 2 — 인터럽트 불가

3.1 인터럽트 불가

인터럽트 불가:

  synchronized 대기 (BLOCKED) 중인 스레드는
  interrupt() 로 깨울 수 없음.

문제:
  - 외부에서 못 깨움
  - 응답성 ↓
  - 종료 신호 무시

3.2 코드

// synchronized — 인터럽트 안 통함
Thread worker = new Thread(() -> {
    synchronized (lock) {   // BLOCKED 대기 중
        doWork();
    }
});
worker.start();

worker.interrupt();   // ❌ BLOCKED 는 안 깨어남
// 락 받을 때까지 계속 대기
// 인터럽트 무시됨

3.3 왜 문제인가

인터럽트 불가 문제:

  앱 종료 시:
    - 스레드들에 인터럽트
    - 정리하고 종료 유도

  하지만 synchronized BLOCKED:
    - 인터럽트 무시
    - 락 받을 때까지 대기
    - 종료 지연

응답성 중요한 경우:
    - 사용자 취소
    - 타임아웃
    - 못 함

3.4 ReentrantLock 의 해결

// ReentrantLock — 인터럽트 가능
ReentrantLock lock = new ReentrantLock();

public void processInterruptible() {
    try {
        lock.lockInterruptibly();   // 인터럽트 가능
        try {
            doWork();
        } finally {
            lock.unlock();
        }
    } catch (InterruptedException e) {
        // 대기 중 인터럽트 → 여기로
        Thread.currentThread().interrupt();
        log.info("Interrupted while waiting for lock");
    }
}

3.5 시각화

인터럽트 불가:

synchronized:
  스레드: [BLOCKED 대기]
          interrupt() → 무시 (계속 대기)

ReentrantLock (lockInterruptibly):
  스레드: [대기]
          interrupt() → InterruptedException (깨어남)

3.6 ILIC 의 맥락

@Service
public class InterruptProblem {
    
    private final Object lock = new Object();
    
    // ❌ synchronized — 인터럽트 불가
    public void processSync(Shipment shipment) {
        synchronized (lock) {   // BLOCKED 시 인터럽트 무시
            doWork(shipment);
        }
    }
    
    // ✓ ReentrantLock — 인터럽트 가능
    private final ReentrantLock reentrantLock = new ReentrantLock();
    
    public void processInterruptible(Shipment shipment) {
        try {
            reentrantLock.lockInterruptibly();   // 인터럽트로 깰 수 있음
            try {
                doWork(shipment);
            } finally {
                reentrantLock.unlock();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.info("Processing cancelled for {}", shipment.getId());
            // 사용자 취소, 종료 신호 등 처리 가능
        }
    }
    
    private void doWork(Shipment s) { }
}

3.7 자기 점검 답변

인터럽트 불가의 문제는?

:
1. 인터럽트 불가:

  • BLOCKED 못 깨움
  • interrupt() 무시
  1. 문제:

    • 응답성 ↓
    • 종료 지연
    • 취소 불가
  2. 해결:

    • lockInterruptibly()
    • InterruptedException
  3. 활용:

    • 사용자 취소
    • graceful shutdown

4️⃣ 한계 3 — 공정성 X

4.1 공정성 X

공정성 (Fairness) 보장 X:

  락 획득 순서가 정의되지 않음.
  - 비공정 (Non-fair)
  - 먼저 대기 ≠ 먼저 획득
  - 새치기 가능

4.2 기아 (Starvation)

기아 (Starvation):

  특정 스레드가 계속 락을 못 받음.

원인:
  - 비공정 락
  - 다른 스레드가 자주 받음
  - 운 나쁜 스레드 무한 대기

문제:
  - 특정 작업 지연
  - 응답 없음

4.3 코드 (한계)

// synchronized — 비공정
public void process() {
    synchronized (lock) {   // 순서 보장 X
        // 먼저 대기한 스레드가 먼저 받는다는 보장 없음
        doWork();
    }
}

// 스레드 A, B, C 경쟁:
// - A 가 자주 받을 수 있음
// - C 가 기아 가능

4.4 ReentrantLock 의 해결

// ReentrantLock — 공정 모드
ReentrantLock fairLock = new ReentrantLock(true);   // 공정

public void processFair() {
    fairLock.lock();   // FIFO 순서 (먼저 대기 먼저 획득)
    try {
        doWork();
    } finally {
        fairLock.unlock();
    }
    // 기아 방지
    // 단, 성능 ↓ (순서 관리 비용)
}

4.5 공정 vs 비공정 트레이드오프

공정 vs 비공정:

공정 (Fair):
  + 순서 보장 (FIFO)
  + 기아 방지
  - 성능 ↓ (순서 관리)

비공정 (Non-fair):
  + 성능 ↑ (처리량)
  + 기본값
  - 기아 가능

선택:
  - 순서 중요: 공정
  - 성능 중요: 비공정 (기본)

4.6 ILIC 의 맥락

@Service
public class FairnessProblem {
    
    private final Object lock = new Object();
    
    // ❌ synchronized — 비공정 (기아 가능)
    public void processNonFair(Shipment shipment) {
        synchronized (lock) {
            // 순서 보장 X
            doWork(shipment);
        }
    }
    
    // ✓ ReentrantLock(true) — 공정 (FIFO)
    private final ReentrantLock fairLock = new ReentrantLock(true);
    
    public void processFair(Shipment shipment) {
        fairLock.lock();   // 먼저 온 순서
        try {
            doWork(shipment);
            // 모든 shipment 가 공평하게 처리
            // 기아 방지
        } finally {
            fairLock.unlock();
        }
    }
    
    private void doWork(Shipment s) { }
}

4.7 자기 점검 답변

공정성 X의 문제는?

:
1. 공정성 X:

  • 순서 미정의
  • 새치기 가능
  1. 기아:

    • 특정 스레드 무한 대기
    • 작업 지연
  2. 해결:

    • ReentrantLock(true)
    • FIFO
  3. 트레이드오프:

    • 공정: 순서, 느림
    • 비공정: 성능, 기아

5️⃣ 운영 환경의 무한 대기 사고

5.1 무한 대기의 실제 사고

운영 환경 무한 대기 사고:

시나리오:
  - 스레드 A 가 락 보유 후
  - 외부 API 호출 (응답 없음, 무한 대기)
  - 다른 모든 스레드 BLOCKED
  - 서버 전체 멈춤

결과:
  - 응답 없음
  - 스레드 풀 고갈
  - 서비스 다운

5.2 스레드 풀 고갈

스레드 풀 고갈:

  톰캣 스레드 200개:
    - 모두 같은 락 대기 (BLOCKED)
    - 새 요청 처리 불가
    - 503 에러

원인:
  - 락 보유 스레드가 안 풀음
  - 무한 대기 (synchronized)

→ 서비스 장애

5.3 진단

# jstack 으로 진단
$ jstack <pid>

# 많은 스레드가 BLOCKED:
"http-thread-1" BLOCKED (on object monitor)
  waiting to lock <0x...> 
"http-thread-2" BLOCKED (on object monitor)
  waiting to lock <0x...> (같은 락)
...

# 락 보유 스레드 확인:
"http-thread-99" RUNNABLE
  - locked <0x...>
  at SocketRead (외부 API 무한 대기)
  # 이 스레드가 락 잡고 멈춤

5.4 예방

// ❌ 위험 — 락 안에서 외부 호출
public void riskyProcess() {
    synchronized (lock) {
        callExternalApi();   // 응답 없으면 무한 대기
        // 다른 스레드 모두 BLOCKED
    }
}

// ✓ 개선 1 — 락 밖에서 외부 호출
public void betterProcess() {
    Result result = callExternalApi();   // 락 밖 (병렬)
    synchronized (lock) {
        updateState(result);   // 메모리 연산만
    }
}

// ✓ 개선 2 — 타임아웃
public boolean timeoutProcess() throws InterruptedException {
    if (reentrantLock.tryLock(5, TimeUnit.SECONDS)) {
        try {
            callExternalApiWithTimeout();   // 자체 타임아웃도
            return true;
        } finally {
            reentrantLock.unlock();
        }
    }
    return false;   // 락 못 얻으면 포기
}

5.5 ILIC 의 맥락

@Service
public class ProductionWaitIncident {
    
    private final Object lock = new Object();
    private final ReentrantLock reentrantLock = new ReentrantLock();
    
    // ❌ 사고 패턴 — 락 안 외부 호출
    public void dangerousPattern(Shipment shipment) {
        synchronized (lock) {
            // 외부 추적 API (응답 없으면?)
            Tracking tracking = trackingApi.fetch(shipment.getBlNo());
            // 만약 멈추면 모든 요청 BLOCKED → 서비스 다운
            update(tracking);
        }
    }
    
    // ✓ 안전 패턴
    public boolean safePattern(Shipment shipment) {
        // 외부 호출 락 밖 + 자체 타임아웃
        Tracking tracking = trackingApi.fetchWithTimeout(
            shipment.getBlNo(), Duration.ofSeconds(5));
        
        // 락은 타임아웃 + 짧은 임계 영역
        try {
            if (reentrantLock.tryLock(3, TimeUnit.SECONDS)) {
                try {
                    update(tracking);   // 메모리 연산
                    return true;
                } finally {
                    reentrantLock.unlock();
                }
            }
            return false;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    private void update(Tracking t) { }
}

5.6 자기 점검 답변

운영 환경 무한 대기 사고는?

:
1. 사고:

  • 락 보유 스레드 멈춤
  • 모든 스레드 BLOCKED
  • 서비스 다운
  1. 스레드 풀 고갈:

    • 모두 대기
    • 새 요청 불가
    • 503
  2. 진단:

    • jstack (BLOCKED 확인)
    • 락 보유 스레드
  3. 예방:

    • 외부 호출 락 밖
    • 타임아웃 (tryLock)

6️⃣ 데드락과 회복 불가

6.1 데드락

데드락 (Deadlock):

  두 스레드가 서로의 락을 기다리며
  영원히 멈춤.

조건:
  - 상호 배제
  - 점유 대기
  - 비선점
  - 순환 대기

6.2 데드락 시나리오

// 데드락 예제
Object lockA = new Object();
Object lockB = new Object();

// 스레드 1
synchronized (lockA) {
    sleep(100);
    synchronized (lockB) {   // lockB 대기
        // ...
    }
}

// 스레드 2
synchronized (lockB) {
    sleep(100);
    synchronized (lockA) {   // lockA 대기
        // ...
    }
}

// 스레드 1: lockA 보유, lockB 대기
// 스레드 2: lockB 보유, lockA 대기
// → 서로 대기 → 데드락

6.3 synchronized 의 회복 불가

synchronized 데드락 회복 불가:

  데드락 발생 시:
    - 두 스레드 모두 BLOCKED
    - 무한 대기 (타임아웃 X)
    - 인터럽트 X (못 깨움)
    - 회복 방법 없음

  결과:
    - 영원히 멈춤
    - 재시작만이 답

6.4 시각화

데드락:

스레드 1: lockA 보유 ──→ lockB 대기 (BLOCKED)
                              ↑
스레드 2: lockB 보유 ──→ lockA 대기 (BLOCKED)
                              ↑
  순환 대기 → 영원히

synchronized:
  - 타임아웃 X → 포기 못 함
  - 인터럽트 X → 못 깨움
  → 회복 불가

6.5 ReentrantLock 의 회복

// ReentrantLock — tryLock 으로 데드락 회피
public boolean transfer(Account from, Account to, int amount) 
        throws InterruptedException {
    if (from.lock.tryLock(1, TimeUnit.SECONDS)) {
        try {
            if (to.lock.tryLock(1, TimeUnit.SECONDS)) {
                try {
                    // 둘 다 획득
                    from.balance -= amount;
                    to.balance += amount;
                    return true;
                } finally {
                    to.lock.unlock();
                }
            }
        } finally {
            from.lock.unlock();
        }
    }
    // 락 못 얻으면 포기 + 재시도
    return false;   // 데드락 회피
}

6.6 ILIC 의 맥락

@Service
public class DeadlockRecovery {
    
    // ❌ synchronized — 데드락 회복 불가
    public void transferSync(ShipmentAccount from, ShipmentAccount to, BigDecimal amount) {
        synchronized (from) {
            synchronized (to) {   // 순서 다르면 데드락
                from.debit(amount);
                to.credit(amount);
            }
        }
        // 데드락 시 회복 불가
    }
    
    // ✓ ReentrantLock — tryLock 으로 회피
    public boolean transferSafe(ShipmentAccount from, ShipmentAccount to, BigDecimal amount) {
        try {
            if (from.lock.tryLock(1, TimeUnit.SECONDS)) {
                try {
                    if (to.lock.tryLock(1, TimeUnit.SECONDS)) {
                        try {
                            from.debit(amount);
                            to.credit(amount);
                            return true;
                        } finally {
                            to.lock.unlock();
                        }
                    }
                } finally {
                    from.lock.unlock();
                }
            }
            return false;   // 락 못 얻음 → 재시도
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
}

6.7 자기 점검 답변

데드락에서 synchronized가 회복 불가한 이유는?

:
1. 데드락:

  • 서로 락 대기
  • 순환
  1. 회복 불가:

    • 타임아웃 X (포기 못 함)
    • 인터럽트 X (못 깨움)
    • 영원히 멈춤
  2. 결과:

    • 재시작만이 답
  3. 해결:

    • tryLock (포기/재시도)
    • 락 순서 일관

7️⃣ 더 정교한 락의 필요성

7.1 Lock 인터페이스

// java.util.concurrent.locks.Lock
public interface Lock {
    void lock();                              // 기본 획득
    void lockInterruptibly() throws ...;      // 인터럽트 가능
    boolean tryLock();                         // 즉시 시도
    boolean tryLock(long time, TimeUnit unit); // 타임아웃
    void unlock();                             // 반납
    Condition newCondition();                  // 조건
}

7.2 한계 극복

Lock 이 synchronized 한계 극복:

synchronized 한계 → Lock 해결:

1. 무한 대기 → tryLock(timeout)
2. 인터럽트 X → lockInterruptibly()
3. 공정성 X → new ReentrantLock(true)
4. 데드락 회복 X → tryLock

7.3 추가 기능

Lock 의 추가 기능:

  - tryLock (즉시/타임아웃)
  - lockInterruptibly
  - 공정성 옵션
  - Condition (정교한 대기/통지)
  - 여러 Condition

→ 정교한 제어

7.4 비용 — try-finally

// Lock 의 비용 — 명시적 unlock
Lock lock = new ReentrantLock();

lock.lock();
try {
    // 임계 영역
} finally {
    lock.unlock();   // ★ 필수 (깜박하면 데드락)
}

// synchronized 는 자동
// Lock 은 수동 (try-finally 필수)

7.5 구현체

Lock 의 구현체:

ReentrantLock:
  - 재진입 락
  - synchronized 대체
  - Unit 5.3

ReadWriteLock / ReentrantReadWriteLock:
  - 읽기/쓰기 분리
  - 읽기는 공유

StampedLock:
  - 낙관적 읽기
  - Java 8+

7.6 ILIC 의 맥락

@Service
public class LockNecessity {
    
    // synchronized 한계 → Lock 으로
    private final ReentrantLock lock = new ReentrantLock();
    
    // 1. 타임아웃
    public boolean withTimeout(Shipment s) throws InterruptedException {
        if (lock.tryLock(5, TimeUnit.SECONDS)) {
            try { process(s); return true; }
            finally { lock.unlock(); }
        }
        return false;
    }
    
    // 2. 인터럽트
    public void interruptible(Shipment s) throws InterruptedException {
        lock.lockInterruptibly();
        try { process(s); }
        finally { lock.unlock(); }
    }
    
    // 3. 즉시 시도
    public boolean tryNow(Shipment s) {
        if (lock.tryLock()) {
            try { process(s); return true; }
            finally { lock.unlock(); }
        }
        return false;   // 이미 처리 중
    }
    
    private void process(Shipment s) { }
}

7.7 자기 점검 답변

더 정교한 락의 필요성은?

:
1. Lock 인터페이스:

  • lock, tryLock
  • lockInterruptibly
  • Condition
  1. 한계 극복:

    • 타임아웃 (tryLock)
    • 인터럽트 (lockInterruptibly)
    • 공정성 (true)
  2. 비용:

    • try-finally 필수
    • 수동 unlock
  3. 구현체:

    • ReentrantLock
    • ReadWriteLock

8️⃣ synchronized vs Lock 선택

8.1 비교

항목synchronizedLock (ReentrantLock)
타임아웃XO (tryLock)
인터럽트XO (lockInterruptibly)
공정성XO (옵션)
반환자동수동 (unlock)
간단함O△ (try-finally)
Conditionwait/notify여러 Condition

8.2 synchronized 선택

synchronized 선택:

  ✓ 단순한 동기화
  ✓ 짧은 임계 영역
  ✓ 타임아웃/인터럽트 불필요
  ✓ 가독성 우선

대부분의 단순한 경우

8.3 Lock 선택

Lock 선택:

  ✓ 타임아웃 필요
  ✓ 인터럽트 필요
  ✓ 공정성 필요
  ✓ 정교한 제어 (Condition)
  ✓ tryLock (데드락 회피)

복잡한 동시성 제어

8.4 권장

권장:

  먼저 synchronized 고려:
    - 간단하면 충분
    - 자동 반환 안전

  Lock 필요 시:
    - 타임아웃/인터럽트/공정성
    - 정교한 제어

  또는 더 높은 추상화:
    - Atomic
    - 동시성 컬렉션
    - Executor (Phase 7)

8.5 현대적 관점

현대적 관점:

  대부분의 경우:
    - 직접 락 < 고수준 도구

  우선순위:
    1. 무상태 (락 불필요)
    2. 불변 객체
    3. 동시성 컬렉션
    4. Atomic
    5. synchronized
    6. Lock (정교한 제어 시)

8.6 ILIC 의 맥락

@Service
public class LockChoiceGuide {
    
    // 1. synchronized — 단순
    private int simpleCounter = 0;
    public synchronized void simple() {
        simpleCounter++;
    }
    
    // 2. ReentrantLock — 정교한 제어
    private final ReentrantLock lock = new ReentrantLock();
    public boolean withControl(Shipment s) throws InterruptedException {
        if (lock.tryLock(5, TimeUnit.SECONDS)) {
            try { process(s); return true; }
            finally { lock.unlock(); }
        }
        return false;
    }
    
    // 3. Atomic — 단일 변수 (권장)
    private final AtomicInteger atomicCounter = new AtomicInteger();
    public void atomic() {
        atomicCounter.incrementAndGet();
    }
    
    // 4. 동시성 컬렉션 (권장)
    private final Map<Long, Shipment> cache = new ConcurrentHashMap<>();
    public void cache(Shipment s) {
        cache.put(s.getId(), s);
    }
    
    private void process(Shipment s) { }
}

8.7 자기 점검 답변

synchronized vs Lock 선택은?

:
1. synchronized:

  • 단순, 짧은 임계
  • 자동 반환
  1. Lock:

    • 타임아웃/인터럽트/공정성
    • 정교한 제어
  2. 권장:

    • 먼저 synchronized
    • 필요 시 Lock
  3. 현대:

    • 무상태 > 컬렉션 > Atomic > sync > Lock

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
synchronized 한계 3가지?무한 대기, 인터럽트 X, 공정성 X
무한 대기?타임아웃 불가
인터럽트 불가?BLOCKED 못 깸
공정성 X?기아 가능
운영 사고?스레드 풀 고갈
데드락 회복?synchronized 불가
synchronized 장점?간단, 자동 반환
Lock 해결?tryLock, lockInterruptibly
언제 synchronized?단순
언제 Lock?정교한 제어

9.2 자기 점검 체크리스트

장점

  • 간단함
  • 자동 반환
  • 재진입

한계 1 — 무한 대기

  • 타임아웃 불가
  • tryLock

한계 2 — 인터럽트

  • BLOCKED 못 깸
  • lockInterruptibly

한계 3 — 공정성

  • 기아
  • ReentrantLock(true)

운영 사고

  • 스레드 풀 고갈
  • 예방

데드락

  • 회복 불가
  • tryLock 회피

선택

  • synchronized vs Lock

9.3 추가 심화 질문

Q1: synchronized 가 여전히 쓰이는 이유?

답:

  • 간단함 (가독성)
  • 자동 반환 (안전)
  • 대부분 충분
  • JVM 최적화 (Java 6+)

Q2: Lock 의 단점?

답:

  • try-finally 필수
  • unlock 깜박 위험
  • 복잡
  • 가독성 ↓

Q3: ReadWriteLock 의 이점?

답:

  • 읽기/쓰기 분리
  • 읽기는 동시 (공유)
  • 쓰기는 배타
  • 읽기 많은 경우 효율

Q4: 데드락 4가지 조건 깨기?

답:

  • 순환 대기 깨기 (락 순서 일관)
  • 점유 대기 깨기 (tryLock)
  • 비선점 깨기 (타임아웃)
  • 가장 실용적: 락 순서 + tryLock

Q5: StampedLock?

답:

  • Java 8+
  • 낙관적 읽기 (lock 없이)
  • 읽기 충돌 적으면 빠름
  • ReadWriteLock 보다 효율 (읽기 위주)

🎯 핵심 요약 — 3줄 정리

1. synchronized 한계 3가지

  • 무한 대기 (타임아웃 X)
  • 인터럽트 불가
  • 공정성 X (기아)

2. 사고와 회복

  • 무한 대기 → 스레드 풀 고갈
  • 데드락 → synchronized 회복 불가

3. 극복과 선택

  • Lock: tryLock, lockInterruptibly, 공정성
  • 단순: synchronized, 정교: Lock

📚 다음으로...

Unit 5.2 — LockSupport (저수준 도구)

이번 Unit에서 synchronized 한계를 봤다면, 다음은 LockSupport (저수준 도구).

  • park / unpark
  • WAITING 상태
  • ReentrantLock 의 내부 도구

Phase 5 진행 상황

🚀 Phase 5 — 정교한 락: LockSupport와 ReentrantLock
  ✅ Unit 5.1 synchronized의 한계 정리 ← 여기
  ⏭ Unit 5.2 LockSupport
  ⏭ Unit 5.3 ReentrantLock
  ⏭ Unit 5.4 tryLock (★ 마스터)

4주차 누적 진행

✅ Phase 1~4 (17 Unit, 1차 정점 완료)
🚀 Phase 5 — Lock 도구 (1/4 진행)

총: 18/35 Unit

🚀 Phase 5 시작 — 정교한 락 진입

profile
Software Developer

0개의 댓글