4주차 Unit 3.5 — join()

Psj·4일 전

F-lab

목록 보기
131/142

Unit 3.5 — join()

F-LAB JAVA · 4주차 · Phase 3 · 스레드 만들고 다루기
🏆 Phase 3 완주 — 스레드 다루기 마스터


📌 학습 목표

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

  • join() 의 정의 와 동작은?
  • join() 시 호출자 스레드의 상태 는?
  • 병렬 실행 vs 직렬 실행 의 차이는?
  • 잘못된 join 패턴 (직렬화) 은?
  • join(ms) 의 동작은?
  • join() 의 내부 구현 (wait 기반) 은?
  • join 외 결과 회수 방법 (Future) 은?
  • 여러 스레드의 join 패턴은?
  • Phase 3 전체 의 종합은?

🎯 핵심 한 문장

join() 은 대상 스레드가 종료될 때까지 현재 (호출한) 스레드를 대기시키는 메서드로, 여러 스레드의 작업 완료를 기다리는 가장 단순한 방법이다.
t.join() 을 호출하면 현재 스레드는 WAITING 상태가 되어 t 가 종료될 때까지 대기하고, t 종료 시 RUNNABLE 로 복귀한다.
병렬 실행 은 모든 스레드를 먼저 start() 한 뒤 각각 join() 하는 것 (전체 시간 ≈ 가장 긴 작업), 직렬 실행 은 start → join 을 하나씩 반복하는 잘못된 패턴 (전체 시간 = 모든 작업의 합).
join(ms) 는 최대 지정 시간만 대기하고, 시간이 지나면 대상이 안 끝났어도 복귀한다 (TIMED_WAITING).
내부적으로 join()wait() 기반으로 구현되며, 결과 값을 반환하지 않으므로 (run 은 void) 결과 회수가 필요하면 Future / CompletableFuture 를 사용한다.

비유 — 친구 기다리기

join() = 친구가 일 끝날 때까지 기다림:

병렬 (올바름):
  - 친구 3명에게 각자 일 시킴 (start × 3)
  - 모두 동시에 일 시작
  - "다 끝났어?" 한 명씩 확인 (join × 3)
  - 가장 오래 걸리는 친구 시간만큼

직렬 (잘못됨):
  - 친구1에게 일 시키고 (start)
  - 끝날 때까지 기다림 (join)
  - 그 다음 친구2 (start → join)
  - 한 명씩 순차 → 모든 시간 합

병렬: 3명 각 1초 → 약 1초
직렬: 3명 각 1초 → 3초

→ join = 종료 대기, 병렬은 start 먼저 + join 나중.


🧭 9개 섹션 로드맵

1. join()의 정의와 동작
2. join() 시 호출자 상태
3. 병렬 실행 패턴
4. 직렬 실행 (잘못된 패턴)
5. join(ms) 타임아웃
6. join()의 내부 구현
7. join 외 결과 회수 (Future)
8. Phase 3 완주 정리
9. 면접 + 자기 점검 + Phase 3 졸업 시험

1️⃣ join()의 정의와 동작

1.1 join() 의 정의

join():

  대상 스레드가 종료될 때까지
  현재 (호출한) 스레드를 대기시킴.

용도:
  - 스레드 완료 대기
  - 결과 회수의 단순 방식
  - 작업 순서 보장

1.2 기본 사용

Thread worker = new Thread(() -> {
    doWork();   // 작업
});

worker.start();   // 시작

worker.join();   // worker 종료까지 대기 (현재 스레드)

System.out.println("Worker done");   // worker 완료 후

1.3 메서드 종류

// 무한 대기
void join() throws InterruptedException;

// 최대 ms 대기
void join(long millis) throws InterruptedException;

// 최대 ms + ns 대기
void join(long millis, int nanos) throws InterruptedException;

// 모두 InterruptedException 던짐

1.4 동작 흐름

join() 흐름:

현재 스레드 (main):
  worker.start()   → worker 시작
  worker.join()    → main 대기 (WAITING)
  ...
  (worker 종료)
  → main RUNNABLE 복귀
  다음 코드

1.5 시각화

join() 동작:

main:    [start][join 대기────][계속]
                       ↑
worker:  ┌─[작업────]─┘
         worker 종료 시 main 깨어남

  main 은 worker 종료까지 대기

1.6 ILIC 의 맥락

public class ShipmentJoinExample {
    
    public void processAndWait(Shipment shipment) throws InterruptedException {
        Thread worker = new Thread(() -> {
            service.process(shipment);
        });
        
        worker.start();   // 처리 시작
        
        // 다른 일 (선택)
        doOtherWork();
        
        worker.join();   // 처리 완료까지 대기
        
        log.info("Processing complete for {}", shipment.getId());
        // worker 완료 후 실행
    }
    
    private void doOtherWork() { }
}

1.7 자기 점검 답변

join()의 정의와 동작은?

:
1. 정의:

  • 대상 종료까지 현재 스레드 대기
  1. 사용:

    • t.join()
    • InterruptedException
  2. 종류:

    • join() (무한)
    • join(ms) (타임아웃)
  3. 흐름:

    • start → join (대기) → 종료 → 복귀

2️⃣ join() 시 호출자 상태

2.1 호출자는 WAITING

join() 호출자 상태:

  t.join() 호출 시
  현재 (호출자) 스레드 → WAITING

  - 대상 t 종료까지 무한 대기
  - join(ms) 면 TIMED_WAITING

2.2 상태 확인

Thread worker = new Thread(() -> {
    sleep(2000);   // 2초 작업
});
worker.start();

// 다른 스레드에서 main 상태 확인
Thread checker = new Thread(() -> {
    sleep(500);
    System.out.println("Main state: " + mainThread.getState());
    // WAITING (join 대기 중)
});
checker.start();

worker.join();   // main → WAITING (worker 종료까지)

2.3 상태 전이

join() 의 상태 전이:

호출자 (main):
  RUNNABLE
    ↓ worker.join()
  WAITING (worker 종료 대기)
    ↓ worker 종료
  RUNNABLE (복귀)

대상 (worker):
  RUNNABLE → ... → TERMINATED
                      ↑ 여기서 main 깨어남

2.4 join(ms) 는 TIMED_WAITING

Thread worker = new Thread(() -> sleep(5000));
worker.start();

worker.join(1000);   // 최대 1초 대기
// main → TIMED_WAITING (1초 또는 worker 종료)

// 1초 후:
// - worker 아직 실행 중 (5초)
// - main 복귀 (시간 초과)
if (worker.isAlive()) {
    System.out.println("Worker still running");
}

2.5 시각화

join 상태:

join() (무한):
  main: RUNNABLE → WAITING → RUNNABLE
                    (worker 종료까지)

join(1000):
  main: RUNNABLE → TIMED_WAITING → RUNNABLE
                    (1초 또는 worker 종료)

2.6 ILIC 의 맥락

public class JoinStateExample {
    
    public void demonstrateState() throws InterruptedException {
        Thread worker = new Thread(() -> {
            try { Thread.sleep(2000); } catch (Exception e) {}
        });
        worker.start();
        
        // 모니터링 스레드
        Thread monitor = new Thread(() -> {
            try {
                Thread.sleep(500);
                // main 은 join 으로 WAITING
                log.info("Main is waiting on worker");
            } catch (Exception e) {}
        });
        monitor.setDaemon(true);
        monitor.start();
        
        worker.join();   // main → WAITING
        log.info("Worker completed, main resumed");
    }
}

2.7 자기 점검 답변

join() 시 호출자 상태는?

:
1. WAITING:

  • join() 무한
  • 대상 종료까지
  1. TIMED_WAITING:

    • join(ms)
    • 시간 또는 종료
  2. 전이:

    • RUNNABLE → WAITING → RUNNABLE
  3. 복귀:

    • 대상 종료 시
    • 또는 시간 초과 (join(ms))

3️⃣ 병렬 실행 패턴

3.1 병렬 실행

병렬 실행 패턴:

  1. 모든 스레드 start() (먼저)
  2. 각각 join() (나중)

효과:
  - 모든 스레드 동시 실행
  - 전체 시간 ≈ 가장 긴 작업

3.2 코드

// 병렬 실행 (올바름)
public void parallel() throws InterruptedException {
    Thread t1 = new Thread(() -> work(3000));   // 3초
    Thread t2 = new Thread(() -> work(2000));   // 2초
    Thread t3 = new Thread(() -> work(2500));   // 2.5초
    
    // 모두 먼저 시작
    t1.start();
    t2.start();
    t3.start();
    
    // 각각 대기
    t1.join();
    t2.join();
    t3.join();
    
    // 전체 시간 ≈ 3초 (가장 긴 t1)
    // (동시 실행)
}

3.3 시각화

병렬 실행:

t1:  [████████] 3초
t2:  [█████]    2초    (동시)
t3:  [██████]   2.5초
     ↑ 모두 동시 시작
              ↑ t1 종료 (가장 늦음)

전체: 약 3초 (가장 긴 작업)

3.4 join 순서는 무관

// join 순서는 중요하지 않음 (병렬이면)
t1.start();
t2.start();
t3.start();

// 어떤 순서로 join 해도 OK
t3.join();   // t3 먼저 join
t1.join();
t2.join();

// 모두 동시 실행 중이므로
// join 순서는 전체 시간에 영향 X
// (가장 긴 작업이 끝나야 모두 끝남)

3.5 여러 스레드 병렬

// 여러 스레드 병렬 (리스트)
public void parallelMany(List<Shipment> shipments) throws InterruptedException {
    List<Thread> threads = new ArrayList<>();
    
    // 모두 시작
    for (Shipment s : shipments) {
        Thread t = new Thread(() -> process(s));
        t.start();
        threads.add(t);
    }
    
    // 모두 대기
    for (Thread t : threads) {
        t.join();
    }
    
    // 모든 처리 완료
    // 전체 시간 ≈ 가장 긴 처리
}

3.6 ILIC 의 맥락

public class ShipmentParallelProcessing {
    
    // 병렬 처리 (올바름)
    public List<Result> processParallel(List<Shipment> shipments) 
            throws InterruptedException {
        List<Thread> threads = new ArrayList<>();
        List<Result> results = Collections.synchronizedList(new ArrayList<>());
        
        // 모두 시작 (병렬)
        for (Shipment s : shipments) {
            Thread t = new Thread(() -> {
                Result r = service.process(s);
                results.add(r);
            });
            t.start();
            threads.add(t);
        }
        
        // 모두 완료 대기
        for (Thread t : threads) {
            t.join();
        }
        
        return results;
        // 전체 시간 ≈ 가장 긴 처리
    }
    
    // 실무: ExecutorService.invokeAll 권장 (Phase 7)
}

3.7 자기 점검 답변

병렬 실행 패턴은?

:
1. 패턴:

  • 모두 start() (먼저)
  • 각각 join() (나중)
  1. 효과:

    • 동시 실행
    • 전체 ≈ 가장 긴 작업
  2. join 순서:

    • 무관 (병렬이면)
  3. 여러 스레드:

    • 리스트에 start 후 join

4️⃣ 직렬 실행 (잘못된 패턴)

4.1 직렬 실행

직렬 실행 (잘못된 패턴):

  start → join 을 하나씩 반복.

문제:
  - 한 스레드 끝나야 다음 시작
  - 사실상 순차 실행
  - 멀티스레드 의미 없음
  - 전체 시간 = 모든 작업의 합

4.2 잘못된 코드

// ❌ 직렬 실행 (잘못됨)
public void serial() throws InterruptedException {
    Thread t1 = new Thread(() -> work(3000));
    Thread t2 = new Thread(() -> work(2000));
    
    t1.start();
    t1.join();   // ★ t1 끝날 때까지 대기 (여기서 블록)
    
    t2.start();   // t1 끝난 후에야 시작
    t2.join();
    
    // 전체 시간 = 3초 + 2초 = 5초 (순차!)
    // 멀티스레드 의미 없음
}

4.3 시각화

직렬 실행 (잘못됨):

t1:  [████████] 3초
              ↑ t1 종료 후
t2:           [█████] 2초

전체: 3초 + 2초 = 5초 (순차)

vs 병렬:
t1:  [████████] 3초
t2:  [█████]    2초 (동시)
전체: 3초

4.4 무엇이 잘못인가

직렬 패턴의 문제:

start → join → start → join

  - 첫 join 에서 블록
  - 다음 start 가 늦어짐
  - 동시 실행 X
  - 순차와 동일

올바름:
  start → start → join → join
  - 모두 시작 후 대기

4.5 비교

// ❌ 직렬 (5초)
t1.start(); t1.join();
t2.start(); t2.join();

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

// 차이:
// - 직렬: 시작과 대기 교차
// - 병렬: 시작 먼저, 대기 나중

4.6 ILIC 의 맥락

public class SerialVsParallel {
    
    // ❌ 직렬 (잘못됨)
    public void processSerial(List<Shipment> shipments) 
            throws InterruptedException {
        for (Shipment s : shipments) {
            Thread t = new Thread(() -> process(s));
            t.start();
            t.join();   // ★ 매번 대기 → 순차
        }
        // 전체 = 모든 처리 합
        // 멀티스레드 의미 없음
    }
    
    // ✓ 병렬 (올바름)
    public void processParallel(List<Shipment> shipments) 
            throws InterruptedException {
        List<Thread> threads = shipments.stream()
            .map(s -> new Thread(() -> process(s)))
            .toList();
        
        threads.forEach(Thread::start);   // 모두 시작
        for (Thread t : threads) {
            t.join();   // 나중에 대기
        }
        // 전체 ≈ 가장 긴 처리
    }
}

4.7 자기 점검 답변

직렬 실행 (잘못된 패턴)은?

:
1. 직렬:

  • start → join 반복
  • 하나씩 순차
  1. 문제:

    • 동시 실행 X
    • 전체 = 작업 합
    • 멀티스레드 무의미
  2. 원인:

    • join 에서 블록
    • 다음 start 늦음
  3. 올바름:

    • start 모두 먼저
    • join 나중에

5️⃣ join(ms) 타임아웃

5.1 join(ms) 의 동작

join(long millis):

  최대 millis 밀리초 대기.
  시간 초과 시 대상이 안 끝났어도 복귀.

동작:
  - 대상 종료 → 즉시 복귀
  - 시간 초과 → 복귀 (대상 계속)

5.2 코드

Thread worker = new Thread(() -> work(5000));   // 5초
worker.start();

worker.join(1000);   // 최대 1초 대기

// 1초 후:
if (worker.isAlive()) {
    System.out.println("Worker still running after 1s");
    // worker 는 계속 실행 (5초)
    // main 은 복귀
}

5.3 활용 — 타임아웃 처리

public boolean processWithTimeout(Shipment shipment, long timeoutMs) 
        throws InterruptedException {
    Thread worker = new Thread(() -> service.process(shipment));
    worker.start();
    
    worker.join(timeoutMs);   // 최대 대기
    
    if (worker.isAlive()) {
        // 타임아웃
        worker.interrupt();   // 인터럽트 시도
        log.warn("Processing timeout for {}", shipment.getId());
        return false;
    }
    return true;   // 완료
}

5.4 join(0) 은 무한

// join(0) = join() (무한 대기)
worker.join(0);   // 무한 (0은 특수)
worker.join();    // 동일

// 주의: join(0) 은 "0초 대기" 가 아니라 "무한"

5.5 시각화

join(1000):

worker:  [████████████] 5초
main:    [join 대기──]
                    ↑ 1초 후 복귀 (worker 계속)

worker.isAlive() == true

vs join() (무한):
worker:  [████████████] 5초
main:    [join 대기────────────]
                              ↑ 5초 후 복귀

5.6 ILIC 의 맥락

public class JoinTimeoutExample {
    
    // 타임아웃 처리
    public ProcessResult processWithLimit(Shipment shipment) 
            throws InterruptedException {
        AtomicReference<Result> result = new AtomicReference<>();
        
        Thread worker = new Thread(() -> {
            result.set(service.process(shipment));
        });
        worker.start();
        
        worker.join(30000);   // 최대 30초
        
        if (worker.isAlive()) {
            // 30초 초과
            worker.interrupt();
            return ProcessResult.timeout(shipment.getId());
        }
        return ProcessResult.success(result.get());
    }
    
    // 실무: Future.get(timeout) 권장 (Phase 7)
}

5.7 자기 점검 답변

join(ms)의 동작은?

:
1. 동작:

  • 최대 ms 대기
  • 시간 초과 시 복귀
  1. 복귀:

    • 대상 종료 → 즉시
    • 시간 초과 → 복귀 (대상 계속)
  2. 상태:

    • TIMED_WAITING
  3. 활용:

    • 타임아웃 처리
    • isAlive 확인
  4. join(0):

    • 무한 (= join())

6️⃣ join()의 내부 구현

6.1 wait 기반

join() 의 내부:

  wait() 기반으로 구현.
  - 대상 스레드 종료 시 notify
  - 호출자 대기 (wait)

6.2 개념적 구현

// Thread.join() 의 개념적 구현 (단순화)
public final synchronized void join(long millis) 
        throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    
    if (millis == 0) {
        while (isAlive()) {
            wait(0);   // 무한 대기 (notify 까지)
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) break;
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

6.3 종료 시 notify

스레드 종료 시:

  스레드가 run() 종료
    ↓
  JVM 이 내부적으로 notifyAll()
    ↓
  join() 중인 스레드들 깨어남
    ↓
  isAlive() == false 확인
    ↓
  join 복귀

6.4 synchronized 와 wait

join() 은 synchronized:

  - Thread 객체의 모니터 락
  - wait() 호출 (락 반납)
  - 종료 시 notifyAll (JVM)

따라서:
  - join 은 Thread 객체에 동기화
  - wait/notify 메커니즘

6.5 spurious wakeup 처리

// while 로 isAlive 재확인 (spurious wakeup 대비)
while (isAlive()) {
    wait(0);
    // 깨어나도 isAlive 재확인
    // 진짜 종료인지 확인
}

// if 가 아니라 while 사용
// (가짜 깨어남 대비)

6.6 ILIC 의 맥락

// join 의 내부 이해 (직접 구현 X, 개념)
public class JoinInternals {
    
    // join 은 wait/notify 기반
    // 직접 비슷하게 구현하면:
    
    private final Object lock = new Object();
    private volatile boolean done = false;
    
    public void waitForCompletion() throws InterruptedException {
        synchronized (lock) {
            while (!done) {   // while (spurious wakeup 대비)
                lock.wait();
            }
        }
    }
    
    public void markDone() {
        synchronized (lock) {
            done = true;
            lock.notifyAll();   // 대기자 깨움
        }
    }
    
    // join 도 이와 유사 (Thread 객체에 동기화)
}

6.7 자기 점검 답변

join()의 내부 구현은?

:
1. wait 기반:

  • wait() 으로 대기
  • 종료 시 notify
  1. 종료 시:

    • JVM 이 notifyAll
    • join 깨어남
  2. synchronized:

    • Thread 객체 모니터
    • wait/notify
  3. spurious wakeup:

    • while (isAlive) 재확인

7️⃣ join 외 결과 회수 (Future)

7.1 join 의 한계

join() 의 한계:

  - 결과 값 반환 X (run 은 void)
  - 완료 대기만
  - 예외 전파 어려움

결과 회수하려면:
  - 공유 변수 (위험)
  - Future / Callable (권장)

7.2 공유 변수 방식 (비권장)

// 공유 변수로 결과 (번거롭고 위험)
public Result processWithSharedVar(Shipment shipment) 
        throws InterruptedException {
    AtomicReference<Result> result = new AtomicReference<>();
    
    Thread worker = new Thread(() -> {
        Result r = service.process(shipment);
        result.set(r);   // 공유 변수에
    });
    worker.start();
    worker.join();   // 완료 대기
    
    return result.get();   // 회수
    // 번거롭고 예외 처리 어려움
}

7.3 Future 방식 (권장)

// Future — 결과 회수 (권장)
public Result processWithFuture(Shipment shipment) throws Exception {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    
    Future<Result> future = executor.submit(() -> {
        return service.process(shipment);   // Callable (반환값)
    });
    
    Result result = future.get();   // 완료 대기 + 결과
    // join + 결과 회수를 한 번에
    
    executor.shutdown();
    return result;
}

7.4 join vs Future

항목join()Future.get()
완료 대기OO
결과 반환XO
예외 전파어려움ExecutionException
타임아웃join(ms)get(timeout)
권장단순 대기결과 필요

7.5 CompletableFuture (더 권장)

// CompletableFuture — 비동기 + 결과 (Phase 8)
public CompletableFuture<Result> processAsync(Shipment shipment) {
    return CompletableFuture.supplyAsync(() -> 
        service.process(shipment));
    // join 불필요, 콜백으로
}

// 여러 작업
public CompletableFuture<List<Result>> processManyAsync(List<Shipment> shipments) {
    List<CompletableFuture<Result>> futures = shipments.stream()
        .map(s -> CompletableFuture.supplyAsync(() -> service.process(s)))
        .toList();
    
    return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
        .thenApply(v -> futures.stream().map(CompletableFuture::join).toList());
    // join 대신 콜백 체이닝
}

7.6 ILIC 의 맥락

public class ResultRetrievalExample {
    
    private final ExecutorService executor = Executors.newFixedThreadPool(4);
    
    // ❌ join + 공유 변수 (번거로움)
    public Result joinApproach(Shipment shipment) throws InterruptedException {
        AtomicReference<Result> ref = new AtomicReference<>();
        Thread t = new Thread(() -> ref.set(service.process(shipment)));
        t.start();
        t.join();
        return ref.get();
    }
    
    // ✓ Future (권장)
    public Result futureApproach(Shipment shipment) throws Exception {
        Future<Result> future = executor.submit(() -> service.process(shipment));
        return future.get();   // 대기 + 결과
    }
    
    // ✓✓ CompletableFuture (현대)
    public CompletableFuture<Result> completableApproach(Shipment shipment) {
        return CompletableFuture.supplyAsync(
            () -> service.process(shipment), executor);
    }
}

7.7 자기 점검 답변

join 외 결과 회수 방법은?

:
1. join 한계:

  • 결과 반환 X
  • 완료 대기만
  • 예외 전파 어려움
  1. 공유 변수:

    • AtomicReference
    • 번거로움 (비권장)
  2. Future (권장):

    • get() 대기 + 결과
    • Callable
  3. CompletableFuture (현대):

    • 콜백
    • join 불필요

8️⃣ Phase 3 완주 정리

8.1 Phase 3 학습 종합

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

Unit 3.1 — 스레드 상태 다이어그램
  - 6가지 상태
  - NEW → RUNNABLE → TERMINATED
  - BLOCKED/WAITING/TIMED_WAITING

Unit 3.2 — Thread 클래스 상속
  - extends Thread
  - start() vs run()
  - start 두 번 (예외)

Unit 3.3 — Runnable 인터페이스
  - 함수형 인터페이스
  - 장점 3가지
  - @Async 내부

Unit 3.4 — 데몬 스레드
  - 보조 스레드
  - JVM 종료 조건
  - 완료 보장 위험

Unit 3.5 — join()
  - 종료 대기
  - 병렬 vs 직렬
  - Future 대안

8.2 Phase 3 의 큰 그림

스레드 다루기:

1. 상태 (생애 주기)
   - 6가지 상태
   - 전이

2. 생성
   - Thread 상속
   - Runnable (권장)

3. 종류
   - 일반 vs 데몬

4. 협력
   - join (완료 대기)

핵심:
  - 스레드 직접 다루기 기초
  - Phase 7 에서 Executor 로 발전

8.3 다음 Phase 예고

Phase 3 → Phase 4:
  - 스레드 생성 → 동기화

Phase 4 — synchronized & volatile (★ 1차 정점):
  - 임계 영역
  - synchronized
  - 모니터 락 (★ 마스터)
  - volatile (★ 마스터)

8.4 Phase 3 핵심 통찰

Phase 3 핵심 통찰 5가지:

1. 6가지 상태
   - NEW → RUNNABLE → TERMINATED

2. start() vs run()
   - 새 스레드 vs 메서드 호출

3. Runnable 권장
   - 분리, 공유, 상속

4. 데몬은 보조
   - 완료 보장 X

5. join 으로 대기
   - 병렬은 start 먼저

8.5 4주차 누적 진행

✅ Phase 1 — 동시성의 기초 (4 Unit)
✅ Phase 2 — 4분면 매트릭스 (3 Unit)
✅ Phase 3 — 스레드 다루기 (5 Unit) ← 완주
⏭ Phase 4 — synchronized & volatile (5 Unit) ★ 1차 정점
⏭ Phase 5 — Lock 도구 (4 Unit)
⏭ Phase 6 — 스레드 협력 (4 Unit)
⏭ Phase 7 — Executor (7 Unit) ★ 2차 정점
⏭ Phase 8 — 고급 비동기 (3 Unit)

총: 12/35 Unit (Phase 3 완주, 약 34%)

8.6 자기 점검 답변

Phase 3 의 종합은?

:
1. 5개 Unit:

  • 상태, Thread 상속
  • Runnable, 데몬, join
  1. 큰 그림:

    • 상태 (생애)
    • 생성 (Thread/Runnable)
    • 종류 (일반/데몬)
    • 협력 (join)
  2. 핵심:

    • Runnable 권장
    • 데몬은 보조
    • 병렬은 start 먼저

9️⃣ 면접 + 자기 점검 + Phase 3 졸업 시험

9.1 면접 단골 질문 매핑

Q핵심 답변
join()?대상 종료까지 대기
join 호출자 상태?WAITING
병렬 패턴?start 먼저, join 나중
직렬 (잘못)?start→join 반복
join(ms)?최대 시간 대기
join 내부?wait 기반
join 결과?반환 X (Future 대안)
병렬 시간?가장 긴 작업
직렬 시간?모든 작업 합
join(0)?무한 (= join())

9.2 Phase 3 졸업 시험 50문항

스레드 상태 (12문항)

Q1. 6가지 상태? → NEW/RUNNABLE/BLOCKED/WAITING/TIMED_WAITING/TERMINATED
Q2. NEW → RUNNABLE? → start()
Q3. BLOCKED? → synchronized 락 대기
Q4. WAITING? → wait/join (무한)
Q5. TIMED_WAITING? → sleep/wait(ms)
Q6. BLOCKED vs WAITING? → 락 반납 vs notify/종료
Q7. RUNNABLE 두 의미? → 실행 중 + 가능
Q8. getState RUNNING? → 없음 (RUNNABLE)
Q9. I/O 대기 상태? → RUNNABLE
Q10. TERMINATED 재시작? → 불가
Q11. sleep vs wait? → 락 유지 vs 반납
Q12. 인터럽트 + WAITING? → InterruptedException

Thread 상속 (12문항)

Q13. Thread 상속? → extends + run 오버라이드
Q14. start()? → 새 스레드 + run
Q15. run() 직접? → 현재 스레드
Q16. 왜 새 스레드 없나? → 그냥 메서드
Q17. start 두 번? → IllegalThreadStateException
Q18. start 비동기? → 즉시 반환
Q19. run 예외? → checked 못 던짐
Q20. UncaughtExceptionHandler? → 미처리 예외
Q21. currentThread? → 현재 스레드
Q22. Thread 한계? → 단일 상속
Q23. setDaemon 시점? → start 전
Q24. 우선순위? → 힌트

Runnable (13문항)

Q25. Runnable? → run() 하나 (SAM)
Q26. 장점 1? → 다른 클래스 상속
Q27. 장점 2? → 작업/스레드 분리
Q28. 장점 3? → 메모리 효율 (공유)
Q29. 람다 가능? → 함수형 인터페이스
Q30. 작업 공유? → 하나 여러 스레드
Q31. @Async 내부? → Runnable/Callable
Q32. Runnable vs Callable? → 반환/예외
Q33. Callable 메서드? → call() throws
Q34. Thread vs Runnable? → Runnable 권장
Q35. 공유 주의? → 상태 (무상태)
Q36. Thread도 Runnable? → 구현함
Q37. 메서드 참조? → this::method

데몬 + join (13문항)

Q38. 데몬? → 보조, 자동 종료
Q39. JVM 종료? → 모든 일반 스레드 종료
Q40. setDaemon? → start 전
Q41. 데몬 용도? → GC, 모니터링
Q42. 메인 종료 = 데몬? → 아니다
Q43. 파일 저장 데몬? → 위험
Q44. 데몬 부적합? → 완료 보장 작업
Q45. join()? → 종료 대기
Q46. join 상태? → WAITING
Q47. 병렬? → start 먼저, join 나중
Q48. 직렬 (잘못)? → start→join 반복
Q49. join(ms)? → 최대 시간
Q50. join 결과? → Future 대안

9.3 채점

50 / 50 → Phase 3 마스터
45-49   → 거의 마스터
40-44   → 복습
< 40    → Unit 3.1 ~ 3.5 재학습

9.4 추가 심화 질문

Q1: join 도중 인터럽트?

답:

  • join() 은 InterruptedException 던짐
  • WAITING 중 인터럽트
  • 처리 후 재시도 또는 종료
  • 플래그 복원 권장

Q2: 자기 자신을 join?

답:

  • t.join() 에서 t 가 현재 스레드면
  • 자기 종료를 기다림 → 데드락
  • 절대 안 됨

Q3: join 과 데드락?

답:

  • A 가 B.join(), B 가 A.join()
  • 서로 종료 대기
  • 데드락
  • 순환 의존 주의

Q4: ExecutorService 가 join 보다 나은 점?

답:

  • 결과 회수 (Future)
  • 스레드 재사용 (풀)
  • 타임아웃, 취소
  • invokeAll (여러 작업)
  • Phase 7

Q5: CompletableFuture.join vs get?

답:

  • join(): unchecked 예외 (CompletionException)
  • get(): checked (ExecutionException, InterruptedException)
  • join 이 스트림에서 편함

🎯 핵심 요약 — 3줄 정리

1. join()

  • 대상 종료까지 현재 스레드 대기 (WAITING)
  • 결과 반환 X (Future 대안)

2. 병렬 vs 직렬

  • 병렬: start 모두 먼저, join 나중 (가장 긴 작업)
  • 직렬 (잘못): start→join 반복 (작업 합)

3. join(ms)와 대안

  • join(ms): 최대 시간 (TIMED_WAITING)
  • 결과 필요: Future / CompletableFuture

🏆 Phase 3 완주 — 스레드 다루기 마스터

🚀 Phase 3 — 스레드 만들고 다루기
  ✅ Unit 3.1 스레드 상태 다이어그램
  ✅ Unit 3.2 Thread 클래스 상속
  ✅ Unit 3.3 Runnable 인터페이스
  ✅ Unit 3.4 데몬 스레드
  ✅ Unit 3.5 join() ← 여기, Phase 3 완주

→ 상태 + 생성 + 종류 + 협력
→ 스레드 직접 다루기 기초 완성

📚 다음으로...

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

Phase 4 — synchronized & volatile (5 Unit)

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 (5 Unit) ★ 1차 정점

총: 12/35 Unit (약 34%)
profile
Software Developer

0개의 댓글