4주차 Unit 4.1 — 임계 영역(Critical Section)과 동기화의 필요성

Psj·2026년 5월 21일

F-lab

목록 보기
132/240

Unit 4.1 — 임계 영역(Critical Section)과 동기화의 필요성

F-LAB JAVA · 4주차 · Phase 4 · 동기화: synchronized와 메모리 가시성
🚀 Phase 4 시작 — ★ 4주차 1차 정점 진입


📌 학습 목표

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

  • 임계 영역 (Critical Section) 의 정의는?
  • 공유 자원 과 임계 영역의 관계는?
  • count++ 가 사실 3단계 (읽기 → 더하기 → 쓰기) 인 이유는?
  • 경쟁 조건 (Race Condition) 의 정의는?
  • 동시 실행 시 결과 손실 이 어떻게 발생하는가?
  • count++ 의 어셈블리/바이트코드 수준 동작은?
  • 원자성 (Atomicity) 의 의미는?
  • 동기화 (Synchronization) 가 필요한 이유는?
  • 동시성 문제의 3가지 유형 은?

🎯 핵심 한 문장

임계 영역 (Critical Section) 은 여러 스레드가 동시에 접근할 때 데이터 불일치가 발생할 수 있는 코드 영역으로, 일반적으로 공유 자원 (인스턴스 변수, static 변수, 공유 객체) 을 수정하는 부분이다.
겉보기에 단순한 count++ 도 사실 읽기 (read) → 1 더하기 (modify) → 쓰기 (write) 의 3단계로 이루어진 비원자적 (non-atomic) 연산이다.
두 스레드가 동시에 count++ 를 실행하면, 둘 다 같은 값을 읽고 각자 1을 더한 뒤 같은 값을 쓰는 경쟁 조건 (Race Condition) 이 발생하여 결과가 손실 된다 (2 증가해야 하는데 1만 증가).
원자성 (Atomicity) 은 연산이 더 이상 쪼개지지 않는 단일 단위로 실행되는 성질이며, 동시성 문제를 막으려면 임계 영역을 원자적으로 만들어야 한다.
이를 위해 동기화 (Synchronization) — 한 번에 하나의 스레드만 임계 영역에 진입하도록 보장하는 메커니즘 (synchronized, Lock 등) 이 필요하다.

비유 — 공동 통장 입금

공유 자원 = 공동 통장 (잔액):

count++ 의 3단계 = 입금 과정:
  1. 읽기: 현재 잔액 확인 (100원)
  2. 더하기: +50원 계산 (150원)
  3. 쓰기: 새 잔액 기록 (150원)

경쟁 조건 (동시 입금):
  - A: 잔액 확인 (100원)
  - B: 잔액 확인 (100원, A 와 동시)
  - A: +50 계산 (150원)
  - B: +30 계산 (130원)
  - A: 150원 기록
  - B: 130원 기록 (A 의 150 덮어씀!)
  → 결과 130원 (180원이어야 하는데 A 의 50 손실)

동기화 = 한 명씩 ATM 사용:
  - A 입금 완료 후 B
  - 손실 없음 (180원)

→ count++ 는 3단계, 동시 실행 시 손실, 동기화로 해결.


🧭 9개 섹션 로드맵

1. 임계 영역의 정의
2. 공유 자원과 임계 영역
3. count++의 3단계
4. 어셈블리/바이트코드 수준
5. 경쟁 조건 (Race Condition)
6. 결과 손실 시나리오
7. 원자성 (Atomicity)
8. 동기화의 필요성
9. 면접 + 자기 점검

1️⃣ 임계 영역의 정의

1.1 임계 영역이란

임계 영역 (Critical Section):

  여러 스레드가 동시에 접근할 때
  데이터 불일치 (race condition) 가
  발생할 수 있는 코드 영역.

특징:
  - 공유 자원 접근/수정
  - 한 번에 하나의 스레드만 안전
  - 동기화 필요

1.2 고전 예제

class Counter {
    int count = 0;
    
    void increment() {
        count++;   // ← 임계 영역 (공유 자원 수정)
    }
}

1.3 임계 영역의 식별

임계 영역 식별:

  공유 자원을 수정하는 코드.

예:
  - 공유 변수 증감 (count++)
  - 공유 컬렉션 수정 (list.add)
  - 공유 객체 상태 변경 (obj.setX)
  - 복합 연산 (확인 후 수정)

식별 핵심:
  - "공유 + 수정"
  - 여러 스레드 접근 가능

1.4 읽기만 하면?

읽기 전용은 임계 영역?:

읽기만 (불변):
  - 동시 읽기 OK
  - 임계 영역 X (보통)

읽기 + 쓰기:
  - 한 스레드라도 쓰면
  - 다른 스레드 읽기/쓰기와 충돌
  - 임계 영역 O

핵심:
  - 쓰기가 있으면 임계 영역
  - 불변 데이터는 안전

1.5 시각화

임계 영역:

여러 스레드 →  ┌─────────────────┐
스레드 A  ───→ │  임계 영역        │
스레드 B  ───→ │  count++         │  ← 동시 접근 위험
스레드 C  ───→ │  (공유 자원 수정)  │
              └─────────────────┘

  → 한 번에 하나만 들어가야 안전
  → 동기화 필요

1.6 ILIC 의 맥락

@Service
public class ShipmentCounterService {
    
    private int totalProcessed = 0;   // 공유 자원 (싱글톤)
    private final List<Shipment> processedList = new ArrayList<>();   // 공유
    
    public void process(Shipment shipment) {
        service.doProcess(shipment);
        
        // ↓ 임계 영역 (공유 자원 수정)
        totalProcessed++;              // 위험!
        processedList.add(shipment);   // 위험!
        // ↑ 여러 스레드 동시 → 데이터 손실
    }
    
    // 읽기 (불변이면 안전)
    public int getCount() {
        return totalProcessed;   // 읽기 (단, 가시성 이슈 — Unit 4.5)
    }
}

1.7 자기 점검 답변

임계 영역의 정의는?

:
1. 정의:

  • 동시 접근 시 데이터 불일치 가능 코드
  • 공유 자원 수정
  1. 식별:

    • 공유 + 수정
    • count++, list.add
  2. 읽기만:

    • 불변이면 안전
    • 쓰기 있으면 임계 영역
  3. 해결:

    • 동기화
    • 한 번에 하나

2️⃣ 공유 자원과 임계 영역

2.1 공유 자원

공유 자원 (Shared Resource):

  여러 스레드가 함께 접근하는 데이터.

종류:
  - 인스턴스 변수 (객체 공유 시)
  - static 변수
  - 공유 객체 (컬렉션 등)
  - 파일, DB 등 외부 자원

2.2 공유 여부가 핵심

임계 영역 = 공유 자원 수정:

공유 안 됨 (지역 변수):
  - 임계 영역 X
  - 안전

공유됨 (인스턴스/static):
  - 수정 시 임계 영역
  - 동기화 필요

(Unit 1.3 의 "스레드 안전 = 공유되는가?" 와 연결)

2.3 공유 자원의 예

public class SharedResources {
    
    // 1. 인스턴스 변수 (객체 공유 시)
    private int instanceVar = 0;   // 공유 가능
    
    // 2. static 변수 (항상 공유)
    private static int staticVar = 0;   // 공유
    
    // 3. 공유 컬렉션
    private final List<String> list = new ArrayList<>();   // 공유
    
    public void method() {
        // 4. 지역 변수 (공유 X)
        int local = 0;   // 안전
        
        // 임계 영역 (공유 자원 수정)
        instanceVar++;     // 위험
        staticVar++;       // 위험
        list.add("x");     // 위험
        local++;           // 안전 (지역)
    }
}

2.4 복합 자원

// 여러 변수의 복합 상태도 임계 영역
public class BankAccount {
    private int balance = 1000;
    private List<Transaction> history = new ArrayList<>();
    
    public void withdraw(int amount) {
        // 임계 영역 (balance + history 일관성)
        if (balance >= amount) {     // 확인
            balance -= amount;       // 수정
            history.add(new Transaction(amount));   // 수정
        }
        // 두 변수가 일관되어야 함
        // 동시 접근 시 불일치 위험
    }
}

2.5 외부 자원

외부 자원도 임계 영역:

파일:
  - 동시 쓰기 충돌
  - 손상 가능

DB:
  - 동시 수정 (트랜잭션으로 보호)
  - 락 메커니즘

외부 API:
  - 상태 변경 시
  - 멱등성 고려

2.6 ILIC 의 맥락

@Service
public class ShipmentSharedResources {
    
    // 공유 자원들
    private final AtomicInteger counter = new AtomicInteger();   // 안전 (Atomic)
    private final Map<Long, Shipment> cache = new ConcurrentHashMap<>();   // 안전
    private final List<Shipment> pending = new ArrayList<>();   // 위험!
    
    public void process(Shipment shipment) {
        // 안전한 공유 자원
        counter.incrementAndGet();
        cache.put(shipment.getId(), shipment);
        
        // 위험한 공유 자원 (임계 영역)
        synchronized (pending) {   // 동기화 필요
            pending.add(shipment);
        }
        
        // 지역 변수 (안전)
        BigDecimal freight = calculate(shipment);   // 안전
    }
    
    private BigDecimal calculate(Shipment s) {
        return s.getWeight();
    }
}

2.7 자기 점검 답변

공유 자원과 임계 영역은?

:
1. 공유 자원:

  • 여러 스레드 접근
  • 인스턴스/static/객체
  1. 공유 여부:

    • 공유 + 수정 = 임계 영역
    • 지역 변수 = 안전
  2. 복합 자원:

    • 여러 변수 일관성
    • 함께 임계 영역
  3. 외부 자원:

    • 파일, DB, API
    • 동시 접근 보호

3️⃣ count++의 3단계

3.1 겉보기 vs 실제

count++ 의 실제:

겉보기:
  count++   // 한 줄, 한 동작?

실제 (3단계):
  1. 읽기 (read): count 값 읽기
  2. 더하기 (modify): 읽은 값 + 1
  3. 쓰기 (write): 결과를 count 에

→ 비원자적 (3개의 분리된 연산)

3.2 단계 분해

// count++ 는 사실:
int temp = count;   // 1. 읽기
temp = temp + 1;    // 2. 더하기
count = temp;       // 3. 쓰기

// 또는:
count = count + 1;
//       ↑읽기  ↑더하기
// ↑쓰기

3.3 왜 문제인가

3단계가 문제인 이유:

  각 단계 사이에 다른 스레드가 끼어들 수 있음.

  스레드 A: 읽기 (count=0)
  → 컨텍스트 스위칭
  스레드 B: 읽기 (count=0)  ← A 의 결과 아직 안 쓰임
  스레드 B: 더하기 (1)
  스레드 B: 쓰기 (count=1)
  → 스위칭
  스레드 A: 더하기 (1)  ← 여전히 0 기반
  스레드 A: 쓰기 (count=1)  ← B 의 1 덮어씀

  결과: count=1 (2여야 하는데!)

3.4 시각화

count++ 동시 실행 (count=0 시작):

스레드 A         스레드 B
  읽기 (0)
                  읽기 (0)    ← A 결과 못 봄
  +1 (1)
                  +1 (1)
  쓰기 (1)
                  쓰기 (1)    ← A 의 1 덮어씀

결과: count = 1
기대: count = 2
→ 1 손실!

3.5 다른 비원자 연산

// 비원자 연산들 (모두 위험)
count++;        // 읽기-더하기-쓰기
count--;        // 읽기-빼기-쓰기
count += 5;     // 읽기-더하기-쓰기
count = count * 2;   // 읽기-곱하기-쓰기

// 복합 연산
if (count == 0) count = 1;   // 확인-후-수정 (check-then-act)
list.add(item);   // 내부적으로 여러 단계

3.6 ILIC 의 맥락

public class CounterProblem {
    
    private int processedCount = 0;
    
    // ❌ 위험 — count++ 3단계
    public void incrementUnsafe() {
        processedCount++;
        // 1. 읽기
        // 2. 더하기
        // 3. 쓰기
        // 동시 실행 시 손실
    }
    
    // 데모 — 손실 확인
    public void demonstrateLoss() throws InterruptedException {
        processedCount = 0;
        int threads = 100;
        int incrementsPerThread = 1000;
        
        List<Thread> workers = new ArrayList<>();
        for (int i = 0; i < threads; i++) {
            Thread t = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    incrementUnsafe();   // 위험
                }
            });
            t.start();
            workers.add(t);
        }
        for (Thread t : workers) t.join();
        
        // 기대: 100,000
        // 실제: < 100,000 (손실)
        log.info("Expected: {}, Actual: {}", 
            threads * incrementsPerThread, processedCount);
    }
}

3.7 자기 점검 답변

count++가 사실 3단계인 이유는?

:
1. 3단계:

  • 읽기 (read)
  • 더하기 (modify)
  • 쓰기 (write)
  1. 비원자:

    • 3개 분리된 연산
    • 사이에 스위칭
  2. 문제:

    • 둘 다 같은 값 읽음
    • 덮어쓰기
    • 손실
  3. 다른 예:

    • count--, +=
    • check-then-act

4️⃣ 어셈블리/바이트코드 수준

4.1 바이트코드로 본 count++

count++ 의 바이트코드 (instanceVar):

  getfield count       // 1. 필드 읽기 (스택에)
  iconst_1             // 2. 상수 1
  iadd                 // 3. 더하기
  putfield count       // 4. 필드 쓰기

  → 여러 바이트코드 명령
  → 각 명령 사이 스위칭 가능

4.2 javap 로 확인

# 컴파일 후 바이트코드 확인
$ javap -c Counter.class

# increment() 메서드:
void increment();
  Code:
     0: aload_0
     1: dup
     2: getfield   #2  // Field count:I    ← 읽기
     5: iconst_1                            ← 1
     6: iadd                                ← 더하기
     7: putfield   #2  // Field count:I    ← 쓰기
    10: return

4.3 CPU 명령 수준

CPU 명령 수준 (개념):

count++ →
  MOV reg, [count]   // 메모리 → 레지스터 (읽기)
  ADD reg, 1         // 레지스터 + 1 (더하기)
  MOV [count], reg   // 레지스터 → 메모리 (쓰기)

  3개의 기계어 명령
  각 명령 사이 인터럽트/스위칭 가능

4.4 원자적 명령

일부 CPU 는 원자적 증감 명령:

  LOCK INC [count]   // x86 의 원자적 증가
  
  - 단일 명령
  - 인터럽트 안 됨
  - 하지만 자바 count++ 는 이걸 안 씀

자바:
  - count++ 는 3단계 바이트코드
  - 원자적 보장 X
  - AtomicInteger 가 CAS 사용 (원자적)

4.5 메모리 접근

메모리 접근 관점:

count++ 시:
  1. 메인 메모리 (또는 캐시) 에서 읽기
  2. 레지스터에서 계산
  3. 메모리 (또는 캐시) 에 쓰기

문제:
  - 읽기와 쓰기 사이 시간
  - 다른 스레드 개입
  - 캐시 일관성 (Unit 4.5)

4.6 ILIC 의 맥락

public class BytecodeLevel {
    
    private int count = 0;
    
    // count++ 의 실제 동작
    public void increment() {
        count++;
        // 바이트코드:
        // getfield count  (읽기)
        // iconst_1
        // iadd            (더하기)
        // putfield count  (쓰기)
        // → 3단계, 위험
    }
    
    // 원자적 대안 (AtomicInteger)
    private final AtomicInteger atomicCount = new AtomicInteger();
    
    public void incrementAtomic() {
        atomicCount.incrementAndGet();
        // 내부적으로 CAS (Compare-And-Swap)
        // 원자적 (하드웨어 지원)
    }
}

4.7 자기 점검 답변

count++의 어셈블리/바이트코드 수준 동작은?

:
1. 바이트코드:

  • getfield (읽기)
  • iconst_1, iadd (더하기)
  • putfield (쓰기)
  1. CPU 명령:

    • MOV (읽기)
    • ADD (더하기)
    • MOV (쓰기)
  2. 확인:

    • javap -c
  3. 원자적 대안:

    • AtomicInteger (CAS)
    • LOCK INC (하드웨어)

5️⃣ 경쟁 조건 (Race Condition)

5.1 경쟁 조건의 정의

경쟁 조건 (Race Condition):

  여러 스레드가 공유 자원에 동시 접근할 때,
  실행 순서 (타이밍) 에 따라
  결과가 달라지는 현상.

특징:
  - 비결정적 (실행마다 다름)
  - 재현 어려움
  - 디버깅 어려움

5.2 발생 조건

경쟁 조건 발생 조건:

1. 공유 자원
   - 여러 스레드 접근

2. 동시 접근
   - 적어도 하나 쓰기

3. 비원자 연산
   - 여러 단계

세 조건 충족 시 경쟁 조건

5.3 두 가지 유형

경쟁 조건 유형:

1. Read-Modify-Write
   - count++
   - 읽고 수정하고 쓰기
   - 중간 개입

2. Check-Then-Act
   - if (x == null) x = new ...
   - 확인 후 행동
   - 확인과 행동 사이 개입

5.4 Check-Then-Act 예

// ❌ Check-Then-Act 경쟁 조건
public class LazyInit {
    private Resource resource;
    
    public Resource getResource() {
        if (resource == null) {       // 확인
            resource = new Resource();   // 행동
        }
        return resource;
        // 두 스레드가 동시에 null 확인
        // → 둘 다 new (인스턴스 2개)
    }
}

// ✓ 동기화
public synchronized Resource getResourceSafe() {
    if (resource == null) {
        resource = new Resource();
    }
    return resource;
}

5.5 비결정성

경쟁 조건의 비결정성:

  같은 코드를 여러 번 실행:
    - 실행 1: 결과 100
    - 실행 2: 결과 98
    - 실행 3: 결과 100
    - 실행 4: 결과 95

  타이밍에 따라 다름
  → 재현 어려움
  → "가끔 버그"

5.6 ILIC 의 맥락

public class RaceConditionExample {
    
    private int stock = 100;
    
    // ❌ 경쟁 조건 (재고 차감)
    public boolean reserveStock(int quantity) {
        if (stock >= quantity) {       // 확인
            // 다른 스레드가 여기서 차감 가능
            stock -= quantity;          // 행동
            return true;
        }
        return false;
        // 두 스레드가 동시에 stock=100 확인
        // 둘 다 통과 → 음수 재고 가능
    }
    
    // ✓ 동기화
    public synchronized boolean reserveStockSafe(int quantity) {
        if (stock >= quantity) {
            stock -= quantity;
            return true;
        }
        return false;
        // 한 번에 하나만 → 안전
    }
}

5.7 자기 점검 답변

경쟁 조건의 정의는?

:
1. 정의:

  • 실행 순서에 따라 결과 변함
  • 비결정적
  1. 조건:

    • 공유 자원
    • 동시 접근 (쓰기)
    • 비원자
  2. 유형:

    • Read-Modify-Write
    • Check-Then-Act
  3. 특징:

    • 재현 어려움
    • 가끔 버그

6️⃣ 결과 손실 시나리오

6.1 손실 시나리오 상세

결과 손실 (count=0, A·B 가 각 +1):

시간  스레드 A          스레드 B         count
 t1   읽기 (0)                            0
 t2                    읽기 (0)           0
 t3   +1 → 1                              0
 t4                    +1 → 1             0
 t5   쓰기 (1)                            1
 t6                    쓰기 (1)           1  ← A 의 1 덮어씀

결과: count = 1
기대: count = 2
→ A 의 증가 손실 (lost update)

6.2 손실의 빈도

손실 빈도:

  적은 동시성:
    - 가끔 손실
    - 운 좋으면 정상

  높은 동시성:
    - 자주 손실
    - 100만 증가 → 90만대

  핵심:
    - 동시성 높을수록 손실 ↑
    - 비결정적

6.3 손실 데모

public class LostUpdateDemo {
    
    private int count = 0;
    
    public static void main(String[] args) throws InterruptedException {
        LostUpdateDemo demo = new LostUpdateDemo();
        
        int threadCount = 10;
        int incrementsPerThread = 100_000;
        
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < threadCount; i++) {
            Thread t = new Thread(() -> {
                for (int j = 0; j < incrementsPerThread; j++) {
                    demo.count++;   // 비동기화
                }
            });
            t.start();
            threads.add(t);
        }
        for (Thread t : threads) t.join();
        
        // 기대: 1,000,000
        // 실제: 보통 < 1,000,000 (예: 870,000)
        System.out.println("Expected: " + (threadCount * incrementsPerThread));
        System.out.println("Actual: " + demo.count);
    }
}

6.4 손실 외 다른 문제

동시성 문제 (손실 외):

1. 잃어버린 갱신 (Lost Update)
   - count++ 손실

2. 더티 읽기 (Dirty Read)
   - 미완성 데이터 읽음

3. 비일관 상태
   - 복합 연산 중간 상태

4. 가시성 문제
   - 변경이 안 보임 (Unit 4.5)

6.5 시각화 — 정상 vs 손실

정상 (동기화):
  A: 읽기(0)→+1→쓰기(1)
  B:                  읽기(1)→+1→쓰기(2)
  결과: 2 ✓

손실 (비동기화):
  A: 읽기(0)→+1→쓰기(1)
  B:    읽기(0)→+1→쓰기(1)
  결과: 1 ✗ (손실)

6.6 ILIC 의 맥락

public class ResultLossExample {
    
    private int totalShipments = 0;
    private BigDecimal totalRevenue = BigDecimal.ZERO;
    
    // ❌ 손실 발생
    public void recordShipment(Shipment shipment) {
        totalShipments++;   // 손실 가능
        totalRevenue = totalRevenue.add(shipment.getRevenue());   // 손실 가능
        // 여러 스레드 동시 → 일부 누락
        // 통계 부정확
    }
    
    // ✓ 동기화
    private final Object lock = new Object();
    
    public void recordShipmentSafe(Shipment shipment) {
        synchronized (lock) {
            totalShipments++;
            totalRevenue = totalRevenue.add(shipment.getRevenue());
        }
        // 정확한 통계
    }
    
    // ✓ Atomic
    private final AtomicInteger atomicCount = new AtomicInteger();
    
    public void recordCount() {
        atomicCount.incrementAndGet();   // 원자적
    }
}

6.7 자기 점검 답변

결과 손실은 어떻게 발생하는가?

:
1. 시나리오:

  • 둘 다 같은 값 읽음
  • 각자 +1
  • 나중 쓰기가 덮어씀
  1. 빈도:

    • 동시성 높을수록 ↑
    • 비결정적
  2. 다른 문제:

    • 더티 읽기
    • 비일관 상태
    • 가시성
  3. 해결:

    • 동기화
    • Atomic

7️⃣ 원자성 (Atomicity)

7.1 원자성의 정의

원자성 (Atomicity):

  연산이 더 이상 쪼개지지 않는
  단일 단위로 실행되는 성질.

특징:
  - 전부 실행 또는 전혀 안 함
  - 중간 상태 없음
  - 다른 스레드 개입 불가

7.2 원자적 vs 비원자적

원자적 vs 비원자적:

원자적 (Atomic):
  - 단일 단위
  - 중간 개입 X
  - 예: 기본 타입 읽기/쓰기 (int)
  - AtomicInteger.incrementAndGet()

비원자적 (Non-atomic):
  - 여러 단계
  - 중간 개입 가능
  - 예: count++ (3단계)
  - long/double 쓰기 (64비트, 일부)

7.3 자바의 원자적 연산

자바에서 원자적인 것:

원자적:
  - 기본 타입 읽기/쓰기 (int, boolean 등)
  - 참조 읽기/쓰기
  - volatile long/double 읽기/쓰기

비원자적:
  - count++ (읽기-수정-쓰기)
  - long/double 쓰기 (volatile 없으면, 일부 JVM)
  - 복합 연산

7.4 long/double 의 특이점

long/double 의 비원자성:

  64비트 값.
  일부 32비트 JVM 에서:
    - 상위 32비트, 하위 32비트 분리 쓰기
    - 중간에 다른 스레드 읽으면
    - 깨진 값 (word tearing)

해결:
  - volatile long/double
  - → 원자적 보장

7.5 원자성 만들기

// 1. synchronized
public synchronized void increment() {
    count++;   // 전체가 원자적 (락)
}

// 2. AtomicInteger
private final AtomicInteger count = new AtomicInteger();
public void increment() {
    count.incrementAndGet();   // CAS 원자적
}

// 3. Lock
private final Lock lock = new ReentrantLock();
public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

7.6 ILIC 의 맥락

public class AtomicityExample {
    
    // 비원자 (위험)
    private int counter = 0;
    public void unsafe() {
        counter++;   // 비원자
    }
    
    // 원자적 방법들
    
    // 1. AtomicInteger
    private final AtomicInteger atomicCounter = new AtomicInteger();
    public void atomic() {
        atomicCounter.incrementAndGet();   // 원자적
    }
    
    // 2. AtomicLong (큰 수)
    private final AtomicLong revenue = new AtomicLong();
    public void addRevenue(long amount) {
        revenue.addAndGet(amount);   // 원자적
    }
    
    // 3. synchronized (복합 연산)
    private int stock = 100;
    public synchronized boolean reserve(int qty) {
        if (stock >= qty) {   // 확인 + 차감이 원자적
            stock -= qty;
            return true;
        }
        return false;
    }
}

7.7 자기 점검 답변

원자성의 의미는?

:
1. 정의:

  • 쪼개지지 않는 단일 단위
  • 전부 또는 전혀
  1. 원자적 (자바):

    • 기본 타입 읽기/쓰기
    • 참조
    • volatile long/double
  2. 비원자:

    • count++ (3단계)
    • long/double (일부)
    • 복합 연산
  3. 만들기:

    • synchronized
    • Atomic
    • Lock

8️⃣ 동기화의 필요성

8.1 동기화란

동기화 (Synchronization):

  여러 스레드의 임계 영역 접근을 제어하여
  한 번에 하나의 스레드만 진입하도록 보장.

목적:
  - 경쟁 조건 방지
  - 데이터 일관성
  - 원자성 보장

8.2 동기화의 효과

동기화의 효과:

상호 배제 (Mutual Exclusion):
  - 한 번에 하나만 임계 영역
  - 동시 접근 차단

가시성 (Visibility):
  - 변경이 다른 스레드에 보임
  - (synchronized, volatile)

순서 (Ordering):
  - 연산 순서 보장
  - happens-before

8.3 동기화 도구

자바의 동기화 도구:

1. synchronized
   - 메서드/블록
   - 모니터 락
   - Unit 4.2~4.4

2. volatile
   - 가시성
   - Unit 4.5

3. Lock (ReentrantLock 등)
   - 명시적 락
   - Phase 5

4. Atomic 클래스
   - CAS 기반
   - 원자적 연산

5. 동시성 컬렉션
   - ConcurrentHashMap 등

8.4 동기화의 비용

동기화의 비용:

성능:
  - 락 획득/반납 오버헤드
  - 대기 (BLOCKED)
  - 직렬화 (병렬성 ↓)

따라서:
  - 필요한 곳만
  - 범위 최소화
  - 적절한 도구

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

8.5 동기화 vs 무상태

동기화 대안 — 무상태:

동기화:
  - 공유 상태 보호
  - 락 비용

무상태:
  - 공유 상태 없음
  - 동기화 불필요
  - 가장 안전

권장:
  - 가능하면 무상태
  - 불가피하면 동기화

8.6 ILIC 의 맥락

@Service
public class SynchronizationNeed {
    
    // 방법 1: 무상태 (권장)
    public BigDecimal calculate(Shipment shipment) {
        BigDecimal weight = shipment.getWeight();   // 지역
        return weight.multiply(RATE);   // 공유 상태 X
    }
    
    // 방법 2: Atomic (단순 카운터)
    private final AtomicInteger counter = new AtomicInteger();
    public void count() {
        counter.incrementAndGet();
    }
    
    // 방법 3: 동시성 컬렉션
    private final Map<Long, Shipment> cache = new ConcurrentHashMap<>();
    public void cache(Shipment s) {
        cache.put(s.getId(), s);
    }
    
    // 방법 4: synchronized (복합 연산)
    private int stock = 100;
    public synchronized boolean reserve(int qty) {
        if (stock >= qty) {
            stock -= qty;
            return true;
        }
        return false;
    }
    
    private static final BigDecimal RATE = BigDecimal.valueOf(0.15);
}

8.7 자기 점검 답변

동기화가 필요한 이유는?

:
1. 목적:

  • 경쟁 조건 방지
  • 데이터 일관성
  • 원자성
  1. 효과:

    • 상호 배제
    • 가시성
    • 순서
  2. 도구:

    • synchronized
    • volatile, Lock
    • Atomic, 동시성 컬렉션
  3. 비용:

    • 성능 (락, 직렬화)
    • 무상태 우선

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
임계 영역?동시 접근 시 불일치 가능 코드
공유 자원?여러 스레드 접근 데이터
count++ 단계?읽기-더하기-쓰기 (3단계)
바이트코드?getfield-iadd-putfield
경쟁 조건?순서에 따라 결과 변함
결과 손실?둘 다 읽고 덮어씀
Check-Then-Act?확인-행동 사이 개입
원자성?쪼개지지 않는 단위
자바 원자적?기본 타입 읽기/쓰기
동기화 필요?경쟁 조건 방지

9.2 자기 점검 체크리스트

임계 영역

  • 정의
  • 공유 자원
  • 식별 (공유+수정)

count++

  • 3단계
  • 바이트코드
  • 비원자

경쟁 조건

  • 정의
  • 조건
  • 유형 (RMW, CTA)

결과 손실

  • 시나리오
  • 빈도
  • 데모

원자성

  • 정의
  • 자바 원자적
  • 만들기

동기화

  • 필요성
  • 도구
  • 비용

9.3 추가 심화 질문

Q1: 왜 count++ 가 한 동작처럼 보이나?

답:

  • 소스 코드 한 줄
  • 하지만 컴파일 시 여러 바이트코드
  • 추상화의 함정
  • 실제론 read-modify-write

Q2: 단일 코어에서도 경쟁 조건?

답:

  • 가능
  • 타임 슬라이스 중 스위칭
  • count++ 중간에 다른 스레드
  • 멀티코어가 아니어도 발생

Q3: AtomicInteger 가 원자적인 원리?

답:

  • CAS (Compare-And-Swap)
  • 하드웨어 명령 (CMPXCHG)
  • 예상 값과 비교 후 교체
  • 락 없이 원자적 (lock-free)

Q4: 가시성과 원자성의 차이?

답:

  • 원자성: 연산 분리 안 됨
  • 가시성: 변경이 다른 스레드에 보임
  • count++ 는 둘 다 문제
  • volatile: 가시성만, synchronized: 둘 다

Q5: ABA 문제?

답:

  • CAS 의 함정
  • A → B → A 변경 시
  • CAS 는 A 로 보임 (변경 못 감지)
  • AtomicStampedReference 로 해결

🎯 핵심 요약 — 3줄 정리

1. 임계 영역과 count++

  • 임계 영역: 공유 자원 수정 코드
  • count++: 읽기-더하기-쓰기 (3단계, 비원자)

2. 경쟁 조건과 손실

  • 둘 다 같은 값 읽고 덮어씀
  • 결과 손실 (lost update)
  • 비결정적

3. 원자성과 동기화

  • 원자성: 쪼개지지 않는 단위
  • 동기화: 한 번에 하나 (synchronized/Atomic/Lock)

📚 다음으로...

Unit 4.2 — synchronized 메서드

이번 Unit에서 동기화의 필요성을 봤다면, 다음은 synchronized 메서드.

  • synchronized 키워드
  • this 잠금
  • static synchronized (Class 잠금)

Phase 4 진행 상황

🚀 Phase 4 — synchronized & volatile (★ 1차 정점)
  ✅ Unit 4.1 임계 영역과 동기화의 필요성 ← 여기
  ⏭ Unit 4.2 synchronized 메서드
  ⏭ Unit 4.3 synchronized 블록
  ⏭ Unit 4.4 모니터 락 (★ 마스터)
  ⏭ Unit 4.5 volatile (★ 마스터)

4주차 누적 진행

✅ Phase 1 — 동시성의 기초 (4 Unit)
✅ Phase 2 — 4분면 매트릭스 (3 Unit)
✅ Phase 3 — 스레드 다루기 (5 Unit)
🚀 Phase 4 — synchronized & volatile (1/5 진행) ★ 1차 정점

총: 13/35 Unit
profile
Software Developer

0개의 댓글