🎯 F-lab Java 4주차 학습 커리큘럼

4주차 자료의 모든 토픽을 "기초 → 동기화 → 협력 → 추상화" 순서로 재배열한 학습 경로.
1~3주차에서 단일 스레드 흐름을 봤다면, 4주차는 여러 스레드가 동시에 움직이는 세계 의 모든 것을 다룬다.
면접·실무에서 가장 자주 등장하는 영역이며, 분량도 가장 많다.


📊 학습 경로 한눈에 보기

[Phase 1] 동시성의 기초 — 프로세스와 스레드
   ↓
[Phase 2] Sync/Async × Blocking/Non-Blocking 4분면
   ↓
[Phase 3] 스레드 만들고 다루기
   ↓
[Phase 4] 동기화 — synchronized와 메모리 가시성  ◄── 4주차 1차 정점
   ↓
[Phase 5] 정교한 락 — LockSupport와 ReentrantLock
   ↓
[Phase 6] 스레드 간 협력 — 생산자/소비자, 인터럽트, yield
   ↓
[Phase 7] 직접 사용의 한계와 Executor 프레임워크  ◄── 4주차 2차 정점
   ↓
[Phase 8] 고급 비동기 — CompletableFuture와 ForkJoinPool

총 8 Phase × 35 Unit — 실습 비중이 매우 높아 압축 7일 또는 여유 14일 권장.

🔗 1·2·3주차와의 관계

영역1주차2주차3주차4주차 (지금)
메모리 모델Heap/StackMethod Area 3분할(없음)스레드별 캐시 vs 메인 메모리, volatile
컬렉션개요내부 구조전체 지도ConcurrentHashMap, ConcurrentLinkedQueue
I/O개요(없음)NIO/Stream/ChannelBlocking vs Non-Blocking 깊이
신규OOPReflection람다/스트림synchronized, Lock, Executor, CompletableFuture, ForkJoin

🗓️ 권장 학습 일정 (압축 7일)

DayPhase학습 목표
1일차Phase 1 + 2프로세스/스레드 기초 + 4분면 매트릭스
2일차Phase 3Thread/Runnable, 데몬, join()
3일차Phase 4synchronized + volatile (★ 1차 정점)
4일차Phase 5LockSupport, ReentrantLock, tryLock
5일차Phase 6wait/notify, 인터럽트, yield
6일차Phase 7Executor 프레임워크 전체 (★ 2차 정점)
7일차Phase 8CompletableFuture, ForkJoinPool

여유 일정 (14일): Phase 4·7은 각 2일씩 배정. 동시성은 코드를 직접 돌려봐야 체화됨.


📚 Phase 1 — 동시성의 기초: 프로세스와 스레드

목표: 멀티태스킹·멀티프로세싱·프로세스·스레드의 정의와 메모리 구조를 명확히 잡는다.

Unit 1.1 — 멀티태스킹 vs 멀티프로세싱

선수 지식: 1주차 Phase 4 (JVM)

핵심 개념

구분멀티태스킹멀티프로세싱
정의1개 CPU(코어)가 여러 작업을 번갈아 실행여러 CPU(코어)가 동시에 작업
기반소프트웨어 (시분할 스케줄링)하드웨어 (다중 코어)
예시OS의 멀티프로그램 환경현대 멀티코어 CPU
  • 시분할(Time Sharing): CPU 시간을 잘게 쪼개서 여러 프로그램에 돌아가며 할당 → "동시 실행처럼" 느껴짐
  • 스케줄링: 어떤 프로그램을 얼마나 실행할지 OS가 결정

자기 점검

  • 코어가 1개인 시스템에서도 "동시 실행"이 가능해 보이는 이유는?
  • 멀티태스킹과 멀티프로세싱은 함께 사용 가능한가?

Unit 1.2 — 프로세스와 스레드

선수 지식: Unit 1.1

핵심 개념

프로세스:

  • 실행 중인 프로그램 (디스크의 파일이 메모리로 올라온 것)
  • 독립적인 메모리 공간 보유
  • OS가 별도의 작업 단위로 관리
  • 하나 이상의 스레드를 반드시 포함

스레드:

  • 프로세스 안에서 코드를 한 줄씩 실행하는 흐름
  • "실(thread)이 코드를 위에서 아래로 꿰는" 비유

메모리 구성:

프로세스 메모리
├── 코드 섹션      ┐
├── 데이터 섹션    │ 모든 스레드가 공유
├── 힙(Heap)      ┘
└── 스택(Stack)   ← 스레드마다 개별 할당

자기 점검

  • 프로세스와 스레드를 클래스/인스턴스에 비유하면?
  • 스레드들이 힙은 공유하고 스택은 따로 갖는 이유는?

Unit 1.3 — 변수 종류와 메모리 위치 (4주차 관점)

선수 지식: Unit 1.2, 2주차 Phase 1

핵심 개념

멀티스레드 관점에서의 변수 매핑:

변수저장 위치멀티스레드 관점
지역 변수Stack (스레드별)스레드 안전 (공유 안 됨)
인스턴스 변수Heap공유 → 동기화 필요
클래스 변수 (static)Method Area (Data)공유 → 동기화 필요
전역 변수 (static)Method Area (Data)공유 → 동기화 필요

핵심 통찰: "스레드 안전한가?"의 답은 대부분 "공유되는가?" 와 같다.

자기 점검

  • 메서드 안의 지역 변수만 사용한다면 동기화가 필요한가?
  • static 변수와 인스턴스 변수의 동기화 우려는 같은가, 다른가?

Unit 1.4 — 스케줄링 큐와 컨텍스트 스위칭

선수 지식: Unit 1.2

핵심 개념

스케줄링 큐:

  • OS 내부에 존재하는 대기 줄
  • 각 스레드는 큐에서 자기 차례를 기다림
  • 코어가 N개면 동시에 N개 스레드 실행 가능

컨텍스트 스위칭(Context Switching):

  • 스레드 A → B로 전환 시
  • A의 CPU 레지스터 값들을 메모리에 저장
  • B의 이전 상태를 메모리에서 로드
  • 오버헤드 발생 (잦으면 성능 저하)

자기 점검

  • 스레드를 무한히 많이 만들면 안 되는 이유는?
  • 코어 4개에 스레드 4개와 스레드 100개의 성능 차이는?

📚 Phase 2 — Sync/Async × Blocking/Non-Blocking 4분면

목표: 4가지 용어가 면접에서 자주 헷갈리는데, "무엇이 다른 축인가" 를 명확히 잡는다.

Unit 2.1 — Sync vs Async (작업 순서 관점)

선수 지식: Phase 1

핵심 개념

  • Sync(동기): 작업 A 완료 후에 B 실행 (순서 보장)
  • Async(비동기): A의 완료를 기다리지 않고 B 실행 (순서 무관)

핵심 질문: "다음 작업을 위해 이전 작업의 완료 여부를 확인 하는가?"

자기 점검

  • "DB 쿼리를 비동기로 처리"한다는 말의 의미는?
  • 비동기가 항상 더 빠른가? (힌트: 단일 작업의 처리 시간 vs 시스템 처리량)

Unit 2.2 — Blocking vs Non-Blocking (제어권 관점)

선수 지식: Unit 2.1

핵심 개념

  • Blocking: 호출된 함수가 끝날 때까지 호출자는 제어권을 잃고 대기
  • Non-Blocking: 호출 즉시 제어권 반환 → 호출자는 다른 일 가능

핵심 질문: "OS(또는 호출된 함수)가 제어권을 가져갔는가, 즉시 돌려줬는가?"

제어권의 의미:

  • 함수의 실행 흐름을 제어할 수 있는 권리
  • 제어권이 넘어가면 해당 스레드는 "할 일이 없는 상태" = 블로킹

자기 점검

  • Sync와 Blocking의 차이를 한 문장으로 설명하라
  • Non-Blocking 코드에서 결과를 받으려면 어떻게 해야 하는가? (힌트: polling, callback)

Unit 2.3 — 4분면 매트릭스 (실전 예제)

선수 지식: Unit 2.1, Unit 2.2

핵심 개념

BlockingNon-Blocking
Sync가장 단순 (전통 IO)Polling 방식
AsyncFuture.get()CompletableFuture, Callback

4가지 조합 정리:

  1. Sync + Blocking — 전통 IO (Socket.read())
    • 가장 직관적, 가장 비효율
  2. Sync + Non-Blocking — NIO Polling
    • socketChannel.read() 가 0 반환 시 다른 일
  3. Async + BlockingFuture.get()
    • 작업은 비동기, 결과 받을 때 블로킹
  4. Async + Non-BlockingCompletableFuture
    • 가장 효율적, 콜백으로 결과 처리

자기 점검

  • "Async + Blocking이 진정한 비동기가 아니다"라는 말의 의미는?
  • 어떤 조합이 실무에서 가장 권장되며, 그 이유는?

📚 Phase 3 — 스레드 만들고 다루기

목표: 스레드를 직접 만들고 상태를 추적하며 제어한다.

Unit 3.1 — 스레드 상태 다이어그램

선수 지식: Phase 1

핵심 개념

NEW ──start()──> RUNNABLE ──작업종료──> TERMINATED
                  │  ↑↓ (CPU 스케줄러)
                  │
        ┌─────────┼─────────────┐
        ↓         ↓             ↓
     BLOCKED   WAITING     TIMED_WAITING
   (synchronized) (wait, join)  (sleep, parkNanos)

상태 전이:

  • NEW → RUNNABLE: start() 호출
  • RUNNABLE → BLOCKED: synchronized 락 대기
  • RUNNABLE → WAITING: wait(), join()
  • RUNNABLE → TIMED_WAITING: sleep(ms), wait(ms)
  • * → TERMINATED: run 종료

자기 점검

  • BLOCKED와 WAITING의 차이는? (힌트: 깨어나는 조건)
  • getState()RUNNING을 반환할 수 있는가?

Unit 3.2 — Thread 클래스 상속

선수 지식: Unit 3.1

핵심 개념

  • Thread 클래스 상속 → run() 오버라이드
  • start() 호출로 실제 스레드 시작
  • run()을 직접 호출하면 새 스레드가 생기지 않음 (그냥 메서드 호출)
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread running");
    }
}
new MyThread().start();  // 새 스레드에서 실행 ✅
new MyThread().run();    // 현재 스레드에서 실행 ❌

자기 점검

  • run() 직접 호출과 start() 호출의 차이를 스레드 상태로 설명하라
  • 같은 Thread 인스턴스에 대해 start()를 두 번 호출하면?

Unit 3.3 — Runnable 인터페이스 (왜 더 좋은가)

선수 지식: Unit 3.2

핵심 개념

Thread 상속의 한계:

  • 자바는 단일 상속만 가능 → 다른 클래스 상속 불가
  • 작업과 스레드 제어가 결합됨

Runnable의 장점 3가지:
1. 다른 클래스 상속 가능 (자바 단일 상속 제약 회피)
2. 작업과 스레드 분리 (객체지향적 설계)
3. 메모리 효율 (여러 스레드가 같은 Runnable 공유 가능)

Runnable task = () -> System.out.println("Task");
new Thread(task).start();
new Thread(task).start();  // 같은 작업을 2개 스레드가 실행

자기 점검

  • 람다로 Runnable을 만들 수 있는 이유는? (힌트: 함수형 인터페이스)
  • Spring의 @Async는 내부적으로 Thread vs Runnable 중 무엇을 쓸까?

Unit 3.4 — 데몬 스레드(Daemon Thread)

선수 지식: Unit 3.3

핵심 개념

  • 데몬 스레드: 일반 스레드들이 모두 종료되면 자동으로 함께 종료
  • 일반 스레드: 모든 일반 스레드가 종료되어야 JVM 종료
  • setDaemon(true) 로 설정 (start() 전에)

용도: GC 스레드, 모니터링, 로깅 등 백그라운드 보조 작업

주의: 작업 완료 보장이 필요하면 데몬으로 설정 금지

자기 점검

  • 메인 스레드가 종료된다고 데몬 스레드가 즉시 종료되는가? (힌트: 다른 일반 스레드)
  • 파일 저장 작업을 데몬으로 돌리면 어떤 사고가 가능한가?

Unit 3.5 — join()

선수 지식: Unit 3.4

핵심 개념

  • t.join(): t 스레드가 종료될 때까지 현재 스레드를 대기
  • 결과 회수의 가장 단순한 방식

병렬 실행 vs 직렬 실행:

// 병렬 (3초)
t1.start(); t2.start();
t1.join();  t2.join();

// 직렬 (5초)  ← 잘못된 패턴
t1.start(); t1.join();
t2.start(); t2.join();

자기 점검

  • join() 호출 시 호출자 스레드의 상태는?
  • 결과 회수를 위해 join 외에 다른 방법은? (힌트: Future)

📚 Phase 4 — 동기화: synchronized와 메모리 가시성 (★ 1차 정점)

목표: 멀티스레드의 가장 큰 적인 데이터 불일치 를 막는 가장 기본 도구를 마스터한다.

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

선수 지식: Phase 3

핵심 개념

  • 임계 영역: 여러 스레드가 동시 접근 시 데이터 불일치 가능성이 있는 코드
  • 일반적으로 공유 자원(인스턴스 변수, static 변수, 공유 객체)을 수정하는 부분

고전적 예제:

class Counter {
    int count = 0;
    void increment() { count++; }  // ← 임계 영역
}

count++ 는 사실 읽기 → 1 더하기 → 쓰기 3단계 → 두 스레드가 동시 실행 시 결과 손실

자기 점검

  • count++ 의 어셈블리 수준 동작을 설명해보라
  • "원자성(atomicity)"이라는 말의 의미는?

Unit 4.2 — synchronized 메서드

선수 지식: Unit 4.1

핵심 개념

  • 메서드 시그니처에 synchronized 키워드
  • 같은 객체에 대해 한 번에 하나의 스레드만 진입
  • 잠금 대상: this (해당 인스턴스)
class Counter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
}

자기 점검

  • static synchronized 메서드의 잠금 대상은? (힌트: Class 객체)
  • 같은 클래스의 다른 synchronized 메서드 둘이 동시 호출 가능한가?

Unit 4.3 — synchronized 블록

선수 지식: Unit 4.2

핵심 개념

  • 메서드 일부만 동기화 → 동기화 범위 최소화
  • 잠금 대상을 명시적으로 지정 가능
public void increment() {
    // 비동기 영역
    synchronized (this) {  // ← 임계 영역만 잠금
        count++;
    }
    // 비동기 영역
}

선택 가이드:

  • 메서드 전체가 임계 영역 → synchronized 메서드
  • 일부만 임계 영역 → synchronized 블록 (성능 ↑)

자기 점검

  • synchronized (this)synchronized (new Object()) 의 차이는?
  • 임계 영역을 너무 넓게 잡으면 어떤 문제가?

Unit 4.4 — 모니터 락(Monitor Lock)의 동작

선수 지식: Unit 4.3

핵심 개념

  • 모든 자바 객체는 모니터 락(=intrinsic lock)을 1개 가짐
  • synchronized 진입 시: 락 획득 시도
    • 성공 → RUNNABLE
    • 실패 → BLOCKED 상태로 대기
  • 메서드/블록 종료 시: 락 자동 반환

락 경쟁 시나리오:

  • 100개 스레드가 같은 synchronized 블록 진입 시도
  • 1개만 성공 → 99개는 BLOCKED
  • 락 획득 순서는 자바 표준에 정의 없음 (공정성 X)

synchronized의 단점:

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

자기 점검

  • BLOCKED 상태의 스레드를 깨울 수 있는 방법은? (힌트: 락 반납)
  • "공정성(fairness)"이 보장되지 않으면 어떤 문제가?

Unit 4.5 — volatile (메모리 가시성)

선수 지식: Unit 4.4

핵심 개념

문제 시나리오:

boolean runFlag = true;
// 스레드 A: while (runFlag) { ... }
// 스레드 B: runFlag = false;
// → A가 영원히 종료 안 됨!

원인: 각 CPU 코어는 자기 캐시 메모리 를 사용

  • 스레드 B의 false 변경이 메인 메모리에 반영 안 됨
  • 스레드 A의 캐시는 여전히 true

해결: volatile:

volatile boolean runFlag = true;
  • 읽기·쓰기 시 항상 메인 메모리 직접 접근
  • 캐시 사용 X → 약간 느려지지만 가시성 보장

중요: synchronized 안의 변수는 자동으로 가시성 보장됨

자기 점검

  • volatile이 원자성도 보장하는가? (힌트: NO. count++는 여전히 위험)
  • volatilesynchronized의 차이를 한 문장으로?

📚 Phase 5 — 정교한 락: LockSupport와 ReentrantLock

목표: synchronized의 한계를 ReentrantLock으로 극복하고, 데드락을 회피하는 패턴을 익힌다.

Unit 5.1 — synchronized의 한계 정리

선수 지식: Phase 4

핵심 한계 3가지:
1. 무한 대기: 타임아웃 설정 불가
2. 인터럽트 불가: 대기 중 외부에서 깨울 수 없음
3. 공정성 X: 어떤 스레드가 락을 받을지 보장 없음

해결책: 자바는 더 정교한 락 도구를 제공

자기 점검

  • 운영 환경에서 무한 대기가 어떤 사고로 이어질 수 있는가?
  • 데드락 상황에서 synchronized로는 왜 회복 불가능한가?

Unit 5.2 — LockSupport (저수준 도구)

선수 지식: Unit 5.1

핵심 개념

LockSupport의 3가지 메서드:

  • park(): 현재 스레드를 WAITING 상태로 (주차)
  • parkNanos(nanos): TIMED_WAITING 상태로 (시간 한정)
  • unpark(thread): 대상 스레드를 RUNNABLE로 복귀
Thread t = new Thread(() -> {
    LockSupport.park();
    System.out.println("깨어남");
});
t.start();
Thread.sleep(100);
LockSupport.unpark(t);  // 깨움

특징:

  • 인터럽트로도 깨울 수 있음
  • 하지만 너무 저수준 → 실무에서 직접 사용하지 않음
  • ReentrantLock의 내부 구현 도구

자기 점검

  • park된 스레드가 BLOCKED가 아닌 WAITING인 이유는?
  • 왜 LockSupport를 직접 쓰지 않는가?

Unit 5.3 — ReentrantLock (실무 표준)

선수 지식: Unit 5.2

핵심 개념

  • Lock 인터페이스의 구현체 (java.util.concurrent.locks)
  • synchronized와 같은 효과 + 추가 기능
  • try-finally 필수 (락 반납 보장)
private final Lock lock = new ReentrantLock();

public boolean withdraw(int amount) {
    lock.lock();        // 락 획득
    try {
        // 임계 영역
        balance -= amount;
        return true;
    } finally {
        lock.unlock();  // 반드시 finally에서!
    }
}

왜 finally가 필수인가:

  • 임계 영역 중간에 예외/return 발생 시 → unlock 호출 안 되면 데드락
  • 다른 스레드들이 영원히 락 못 얻음

자기 점검

  • lock()synchronized 의 차이를 한 문장으로?
  • unlock() 을 깜박하면 어떤 사고가?

Unit 5.4 — tryLock()으로 데드락 회피

선수 지식: Unit 5.3

핵심 개념

tryLock()의 두 형태:

  • boolean tryLock(): 즉시 시도, 실패 시 false
  • boolean tryLock(time, unit): 지정 시간만큼 시도
if (!lock.tryLock()) {
    log("이미 처리중인 작업이 있습니다.");
    return false;  // 무한 대기 안 함
}
try {
    // 임계 영역
} finally {
    lock.unlock();
}

데드락 회피 시나리오:

  • A 스레드가 락1 보유, 락2 대기
  • B 스레드가 락2 보유, 락1 대기
  • lock() 이면 영원히 멈춤
  • tryLock() 이면 한 쪽이 포기하고 재시도 → 데드락 회피

자기 점검

  • lock() 만 쓰는 시스템에서 데드락이 발생하면 어떻게 회복하는가?
  • tryLock(5, SECONDS) 가 실패한 후 어떤 전략을 쓸 수 있나?

📚 Phase 6 — 스레드 간 협력 (생산자-소비자, 인터럽트, yield)

목표: 스레드들이 단순히 경쟁하는 것이 아니라 협력 하는 패턴을 익힌다.

Unit 6.1 — 생산자-소비자 문제

선수 지식: Phase 4

핵심 개념

문제 정의:

  • 생산자: 데이터를 만들어 큐에 넣음
  • 소비자: 큐에서 데이터를 꺼내 처리
  • 큐가 꽉 차면 생산자는 대기
  • 큐가 비면 소비자는 대기

실제 사례: 메시지 큐(MQ), 로깅 시스템, 작업 큐

자기 점검

  • 큐가 차고 비는 상황을 일반 락만으로 어떻게 처리하는가?
  • 이 문제가 멀티스레드의 "핵심 문제"라 불리는 이유는?

Unit 6.2 — wait()과 notify()

선수 지식: Unit 6.1

핵심 개념

  • wait(): synchronized 안에서 호출 → 락을 반납하고 WAITING 상태로
  • notify(): WAITING 중인 스레드 1개를 깨움
  • notifyAll(): 모든 WAITING 스레드를 깨움
  • 반드시 synchronized 블록 안에서만 호출 가능
synchronized (lock) {
    while (queue.isEmpty()) {
        lock.wait();  // 락 반납 + 대기
    }
    // 데이터 처리
    lock.notify();  // 다른 스레드 깨움
}

if가 아니라 while?:

  • 깨어났다고 조건이 충족되었다는 보장 없음 (Spurious wakeup)
  • 항상 다시 검사해야 함

자기 점검

  • wait()sleep()의 결정적 차이는? (힌트: 락)
  • notify()notifyAll() 중 어느 것을 권장? (힌트: 안전성)

Unit 6.3 — 인터럽트(Interrupt) 메커니즘

선수 지식: Phase 3

핵심 개념

3가지 메서드 구분:

메서드동작플래그 변화
interrupt()인터럽트 신호 발송true로 설정
isInterrupted()플래그 확인 (변경 X)변화 없음
interrupted() (static)플래그 확인 후 false로true → false

블로킹 메서드의 동작:

  • sleep(), wait(), join() 중에 인터럽트 받으면
  • InterruptedException 발생
  • → 플래그는 자동으로 false로 리셋

즉각 종료 패턴:

while (!Thread.interrupted()) {  // 매번 플래그 체크
    // 일 수행
}

자기 점검

  • interrupt() 만 호출했는데 스레드가 즉시 종료 안 되는 이유는?
  • sleep() 도중 인터럽트가 들어오면 어떻게 되는가?

Unit 6.4 — yield()

선수 지식: Unit 6.3

핵심 개념

  • yield(): "다른 스레드에 CPU를 양보하겠다"는 힌트
  • 상태는 RUNNABLE 유지 (sleep과 다름)
  • 스케줄링 큐에 다시 들어감 → 다른 스레드에 기회

용도: 바쁜 대기(Busy Waiting) 상황에서 CPU 자원 절약

while (!Thread.interrupted()) {
    if (queue.isEmpty()) {
        Thread.yield();  // CPU 양보
        continue;
    }
    // 작업
}

상태 비교:
| 메서드 | 결과 상태 |
|---|---|
| sleep() | RUNNABLE → TIMED_WAITING → RUNNABLE |
| yield() | RUNNABLE → RUNNABLE (잠깐 양보) |
| wait() | RUNNABLE → WAITING → RUNNABLE |

자기 점검

  • yield() 가 OS의 보장된 동작인가? (힌트: 단순 힌트)
  • 빈 루프 + yield 패턴이 적절한 시나리오는?

📚 Phase 7 — Executor 프레임워크 (★ 2차 정점)

목표: 스레드 직접 사용의 모든 한계를 한 번에 해결하는 자바 표준 동시성 도구를 마스터한다.

Unit 7.1 — 직접 사용의 3가지 문제점

선수 지식: Phase 3 ~ 6

핵심 문제:

  1. 생성 비용:

    • 스레드 1개 = 약 1MB 메모리
    • OS 시스템 콜 필요
    • "1000번 호출에 1000개 스레드 생성"은 비현실적
  2. 관리 문제:

    • 무한히 생성하면 자원 고갈
    • 안전 종료 시 모든 스레드 추적 필요
  3. Runnable의 불편함:

    • 반환 값 없음 (void run())
    • 체크 예외 던질 수 없음

해결책 = 스레드 풀 + 반환값 가능한 작업 인터페이스 = Executor 프레임워크

자기 점검

  • 스레드 1개당 1MB가 실무에서 어떤 의미를 갖는가?
  • Runnable로 결과를 받으려면 어떤 우회 방법을 써야 하는가?

Unit 7.2 — Executor / ExecutorService / Executors

선수 지식: Unit 7.1

핵심 개념

3개 타입의 역할 분담:

이름역할
Executor (인터페이스)작업 실행만 (execute(Runnable))
ExecutorService (인터페이스)+ 작업 제어 (submit, shutdown, invokeAll)
Executors (유틸 클래스)ExecutorService 팩토리

관계:

Executor (실행만)
  └─ ExecutorService (실행 + 제어)
       ↑ 생성
       └─ Executors.newFixedThreadPool() 등

자기 점검

  • ExecutorExecutorService 를 분리한 이유는? (힌트: ISP)
  • new ThreadPoolExecutor(...) 대신 Executors.xxx() 를 쓰나?

Unit 7.3 — 스레드 풀의 종류

선수 지식: Unit 7.2

핵심 개념

팩토리 메서드특징적합 상황
newFixedThreadPool(n)고정 n개 스레드일반 서버, 안정성 우선
newCachedThreadPool()필요 시 무한 생성, 60초 후 회수짧은 작업 다수
newSingleThreadExecutor()1개 스레드순서 보장 필요
newScheduledThreadPool(n)주기적/지연 실행스케줄러, 배치

중요 주의:

  • 풀 크기보다 작업이 많으면 → 블로킹 큐에 대기
  • newCachedThreadPool() 은 트래픽 폭주 시 위험 (스레드 폭증)

자기 점검

  • 일반 웹 서버에 적합한 풀은?
  • 5개 스레드 풀에 동시에 10개 작업을 제출하면?

Unit 7.4 — Callable과 Future (Runnable의 한계 극복)

선수 지식: Unit 7.3

핵심 개념

Runnable vs Callable:

항목RunnableCallable
메서드void run()V call() throws Exception
반환값✅ (제네릭)
체크 예외

Future:

  • submit() 의 반환 타입
  • 작업 결과를 나중에 받을 수 있는 약속
  • get() 으로 결과 회수 (블로킹)
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> future = es.submit(() -> {
    Thread.sleep(2000);
    return 42;
});
log("future 즉시 반환 (작업은 백그라운드에서 진행)");
Integer result = future.get();  // 여기서 블로킹

자기 점검

  • submit() 호출 즉시 Future가 반환되는데, 작업은 어디서 실행되는가?
  • future.get() 호출 전에 작업이 이미 끝났다면?

Unit 7.5 — Future.cancel()

선수 지식: Unit 7.4

핵심 개념

cancel(mayInterruptIfRunning):

  • true: 실행 중인 작업에 인터럽트 발생 → 즉시 중단 시도
  • false: 실행 중이면 그냥 둠, 새로 실행은 안 함
  • 둘 다 Future 상태는 CANCELLED 로 변경

이후 get() 호출 시 → CancellationException

자기 점검

  • 작업 안에서 Thread.sleep(1000) 중인데 cancel(true) 호출 시 어떻게 되는가?
  • 인터럽트를 무시하는 작업에 cancel(true) 가 효과 있는가?

Unit 7.6 — invokeAll() / invokeAny()

선수 지식: Unit 7.5

핵심 개념

메서드동작
invokeAll(tasks)모든 작업 완료 까지 대기, 모든 결과 반환
invokeAny(tasks)첫 번째 완료된 작업 의 결과 반환, 나머지 취소

활용 시나리오:

  • invokeAll: 여러 외부 API 호출 후 모든 결과 종합
  • invokeAny: 여러 후보 중 가장 빨리 응답한 것 채택 (예: 미러 서버)

자기 점검

  • invokeAll 도중 한 작업이 예외를 던지면?
  • invokeAny 가 가장 빠른 응답을 어떻게 결정하는가?

Unit 7.7 — shutdown() vs shutdownNow()

선수 지식: Unit 7.6

핵심 개념

메서드새 작업 거절진행 중 작업큐의 작업
shutdown()마무리마무리
shutdownNow()인터럽트포기 (반환됨)

안전 종료 패턴:

es.shutdown();
if (!es.awaitTermination(60, SECONDS)) {
    es.shutdownNow();  // 60초 내 안 끝나면 강제
}

자기 점검

  • 운영 서버 종료 시 어떤 메서드를 써야 데이터 손실이 없는가?
  • shutdownNow() 가 반환하는 List에는 무엇이 담기는가?

📚 Phase 8 — 고급 비동기 (CompletableFuture & ForkJoinPool)

목표: Future의 한계(블로킹 회수)를 넘는 진정한 비동기 + 병렬 처리 도구를 익힌다.

Unit 8.1 — Future의 한계와 CompletableFuture

선수 지식: Unit 7.4

Future의 한계:

  • 결과 회수가 블로킹 (future.get())
  • 여러 비동기 작업의 연결이 불편
  • 콜백 등록 불가

CompletableFuture의 해결:

  • thenApply, thenAccept, thenCombine체이닝
  • 결과 준비되면 콜백 자동 실행 (블로킹 X)
CompletableFuture.supplyAsync(() -> 5)
    .thenApply(x -> x * 2)            // 변환
    .thenCombine(otherFuture, Integer::sum)  // 결합
    .thenAccept(System.out::println); // 소비

System.out.println("Main thread continues...");  // 즉시 실행

자기 점검

  • Future.get()CompletableFuture.thenAccept() 의 본질적 차이는?
  • 외부 API 3개를 병렬 호출하고 결과 합치기는 어떻게?

Unit 8.2 — ForkJoinPool과 work-stealing

선수 지식: Phase 7

핵심 개념

일반 스레드 풀의 한계:

  • 한 작업이 큰 경우 분할이 어려움
  • 스레드별 작업 분배가 정적

ForkJoinPool의 아이디어:

  • 큰 작업을 재귀적으로 분할(Fork) → 작은 작업
  • 각 스레드는 자기 큐 에 작업 보관
  • 놀고 있는 스레드는 다른 스레드의 큐에서 작업을 훔친다(Work-stealing)

효과: 모든 스레드가 쉴 틈 없이 일함 → CPU 사용률 극대화

자기 점검

  • "Work-stealing" 이 일반 큐 분배보다 효율적인 이유는?
  • ForkJoinPool에 어떤 종류의 작업이 적합한가? (힌트: 분할 가능한 CPU 바운드)

Unit 8.3 — RecursiveTask vs RecursiveAction

선수 지식: Unit 8.2

핵심 개념

클래스반환값
RecursiveTask<V>있음 (V)
RecursiveAction없음 (void)

구현 패턴:
1. compute() 오버라이드
2. 작업이 작으면 직접 처리
3. 크면 둘로 나눠서:

  • leftTask.fork() (비동기 시작)
  • rightResult = rightTask.compute() (동기 처리)
  • leftResult = leftTask.join() (대기 후 합산)
class SumTask extends RecursiveTask<Long> {
    protected Long compute() {
        if (작업이 작으면) return 직접계산();
        SumTask left = new SumTask(start, mid);
        SumTask right = new SumTask(mid+1, end);
        left.fork();
        long r = right.compute();
        long l = left.join();
        return l + r;
    }
}

자기 점검

  • fork()compute() 를 동시에 호출하지 않고 나눠 쓰는 이유는?
  • 자바 스트림의 parallelStream() 은 내부적으로 무엇을 사용하는가?

🎓 종합 자기 점검 (4주차 졸업 시험)

동시성 기초

  1. 멀티태스킹과 멀티프로세싱의 차이는?
  2. 프로세스와 스레드의 메모리 구성 차이는?
  3. 컨텍스트 스위칭이 일어날 때 무슨 일이 발생하는가?
  4. 지역/인스턴스/static 변수 중 동기화가 필요한 것은?

Sync/Async × Blocking/Non-Blocking

  1. Sync와 Blocking을 구분하는 기준은? (힌트: 다른 축)
  2. 4가지 조합 중 가장 효율적인 것은? 그 이유는?
  3. "Async + Blocking이 진정한 비동기가 아니다"의 의미는?

스레드 다루기

  1. start()run() 의 차이는?
  2. Thread 상속 vs Runnable 구현 — 후자가 권장되는 3가지 이유는?
  3. 데몬 스레드를 작업 종료 보장이 필요한 곳에 쓰면 안 되는 이유는?

synchronized & volatile

  1. 임계 영역의 정의는?
  2. synchronized 메서드와 블록의 잠금 대상 차이는?
  3. synchronized의 3가지 한계는?
  4. volatile이 해결하는 문제는?
  5. volatile은 원자성을 보장하는가? (힌트: NO)

Lock 도구

  1. ReentrantLock의 lock() 다음에 반드시 try-finally 가 필요한 이유는?
  2. tryLock() 이 데드락을 어떻게 방지하는가?
  3. LockSupport가 실무에서 직접 사용되지 않는 이유는?

협력

  1. 생산자-소비자 문제를 wait/notify로 해결하는 패턴은?
  2. wait()synchronized 안에서만 동작하는 이유는?
  3. interrupt(), interrupted(), isInterrupted() 의 차이는?
  4. yield()sleep() 의 상태 차이는?

Executor

  1. 스레드 직접 사용의 3가지 문제점은?
  2. Executor / ExecutorService / Executors의 역할 차이는?
  3. newCachedThreadPool() 의 위험성은?
  4. Runnable과 Callable의 차이 3가지는?
  5. Future.get()CompletableFuture.thenAccept() 의 차이는?
  6. shutdown()shutdownNow() 중 안전 종료에 적합한 것은?

고급 비동기

  1. CompletableFuture가 Future보다 우월한 점은?
  2. ForkJoinPool의 work-stealing이 일반 풀보다 효율적인 이유는?
  3. RecursiveTask와 RecursiveAction의 차이는?

📌 학습 운영 팁

9-섹션 마스터 프롬프트로 깊이 파야 할 Unit

반드시 깊이 파기 (면접·실무 직결):

  • Unit 2.3 — 4분면 매트릭스 (★★★ 면접 단골)
  • Unit 4.4 — 모니터 락의 동작 (★★★ 면접 단골)
  • Unit 4.5 — volatile (메모리 가시성)
  • Unit 5.4 — tryLock + 데드락 회피 (실무 직결)
  • Unit 7.4 — Callable과 Future (Spring @Async의 기반)
  • Unit 8.1 — CompletableFuture (실무 비동기 표준)

Phase별 진도 체크리스트

[ ] Phase 1 — 동시성 기초 (Unit 1.1~1.4)
[ ] Phase 2 — 4분면 매트릭스 (Unit 2.1~2.3)
[ ] Phase 3 — 스레드 다루기 (Unit 3.1~3.5)
[ ] Phase 4 — synchronized & volatile (Unit 4.1~4.5)  ★ 1차 정점
[ ] Phase 5 — Lock 도구 (Unit 5.1~5.4)
[ ] Phase 6 — 스레드 협력 (Unit 6.1~6.4)
[ ] Phase 7 — Executor 프레임워크 (Unit 7.1~7.7)  ★ 2차 정점
[ ] Phase 8 — 고급 비동기 (Unit 8.1~8.3)
[ ] 종합 자기 점검 31문항 통과

실습 권장 사항

필수 실습 코드:
1. Race condition 직접 재현 (synchronized 없는 카운터 증가)
2. 데드락 시나리오 (락 2개 잡는 두 스레드)
3. wait/notify로 생산자-소비자 구현
4. ExecutorService로 같은 작업을 풀로 처리
5. CompletableFuture 체이닝 (3개 API 호출 합치기)

디버깅 도구:

  • jstack <pid>: 스레드 덤프 (BLOCKED/WAITING 확인)
  • VisualVM: 스레드 상태 시각화
  • -XX:+PrintGCDetails: GC와 스레드의 상호작용

1·2·3·4주차 흐름 정리

  • 1주차: OOP·JVM·GC·컬렉션·I/O 개론
  • 2주차: JVM 내부·바이트코드·G1 GC·Reflection·Iterator
  • 3주차: 컬렉션 전체 지도·해시·제네릭·비교·I/O 깊이·함수형
  • 4주차 (지금): 멀티스레딩·동시성·동기화·Executor·비동기
profile
Software Developer

0개의 댓글