4주차 Unit 3.2 — Thread 클래스 상속

Psj·2026년 5월 20일

F-lab

목록 보기
128/230

Unit 3.2 — Thread 클래스 상속

F-LAB JAVA · 4주차 · Phase 3 · 스레드 만들고 다루기


📌 학습 목표

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

  • Thread 클래스 상속 으로 스레드 만드는 법은?
  • run() 오버라이드 의 역할은?
  • start() 의 동작 (새 스레드 생성) 은?
  • run() 직접 호출 vs start() 의 차이는?
  • run() 직접 호출 시 새 스레드가 안 생기는 이유는?
  • 같은 인스턴스에 start() 두 번 호출하면?
  • start() 와 run() 의 차이를 스레드 상태 로 설명하면?
  • Thread 의 주요 메서드 는?
  • Thread 상속의 한계 는?

🎯 핵심 한 문장

Thread 클래스를 상속하고 run() 메서드를 오버라이드한 뒤 start() 를 호출하면 새 스레드가 생성되어 그 스레드에서 run() 이 실행된다.
start() 는 OS 레벨에서 새 스레드를 생성하고 (NEW → RUNNABLE), 그 새 스레드가 run() 을 실행한다.
run() 을 직접 호출 하면 새 스레드가 생기지 않고 현재 (호출한) 스레드에서 그냥 메서드로 실행 된다 — 멀티스레드가 아니라 일반 메서드 호출.
같은 Thread 인스턴스에 start() 를 두 번 호출 하면 IllegalThreadStateException 이 발생한다 (TERMINATED 또는 이미 시작된 스레드는 재시작 불가).
Thread 상속은 간단하지만, 자바는 단일 상속만 가능 하여 다른 클래스를 상속할 수 없고 작업과 스레드 제어가 결합되는 한계가 있다 (다음 Unit 의 Runnable 이 대안).

비유 — 일꾼 고용

Thread 상속 = 일꾼 클래스 직접 정의:
  class 일꾼 extends Thread {
    void run() { 일하기 }
  }

start() = 일꾼 출근 (새 일꾼 투입):
  - 새 일꾼 (스레드) 고용
  - 일꾼이 독립적으로 일 (run)
  - 사장은 다른 일

run() 직접 호출 = 사장이 직접 일함:
  - 새 일꾼 X
  - 사장 (현재 스레드) 이 직접
  - 일 끝날 때까지 사장 묶임

start() 두 번 = 이미 퇴근한 일꾼 재고용 시도:
  - 불가능 (예외)
  - 새 일꾼 고용해야

→ Thread 상속 = 일꾼 정의, start() = 새 일꾼, run() 직접 = 사장이 직접.


🧭 9개 섹션 로드맵

1. Thread 클래스 상속 방법
2. run() 오버라이드
3. start()의 동작
4. run() 직접 호출 vs start()
5. 왜 run() 직접 호출은 새 스레드가 없나
6. start() 두 번 호출
7. 상태로 설명 (start vs run)
8. Thread의 주요 메서드와 한계
9. 면접 + 자기 점검

1️⃣ Thread 클래스 상속 방법

1.1 기본 패턴

// Thread 상속
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running: " + getName());
    }
}

// 사용
MyThread t = new MyThread();
t.start();   // 새 스레드에서 run() 실행

1.2 Thread 클래스

public class Thread implements Runnable {
    
    public Thread() { }
    public Thread(String name) { }
    public Thread(Runnable target) { }
    
    public void run() { }   // 오버라이드 대상
    public synchronized void start() { }   // 새 스레드 시작
    
    public final String getName() { }
    public final void setName(String name) { }
    public Thread.State getState() { }
    public final boolean isAlive() { }
    // ...
}

1.3 생성자 활용

class WorkerThread extends Thread {
    
    private final String taskName;
    
    public WorkerThread(String taskName) {
        super("worker-" + taskName);   // 스레드 이름
        this.taskName = taskName;
    }
    
    @Override
    public void run() {
        System.out.println("Processing: " + taskName);
    }
}

// 사용
WorkerThread t = new WorkerThread("shipment-1");
t.start();

1.4 익명 클래스 / 람다

// 익명 클래스
Thread t1 = new Thread() {
    @Override
    public void run() {
        System.out.println("Anonymous thread");
    }
};
t1.start();

// 람다 (Runnable 전달) — 다음 Unit 정밀
Thread t2 = new Thread(() -> {
    System.out.println("Lambda thread");
});
t2.start();

1.5 ILIC 의 맥락

// Thread 상속 (예시 — 실무는 Executor 권장)
public class ShipmentProcessorThread extends Thread {
    
    private final Shipment shipment;
    
    public ShipmentProcessorThread(Shipment shipment) {
        super("shipment-processor-" + shipment.getId());
        this.shipment = shipment;
    }
    
    @Override
    public void run() {
        log.info("Processing shipment: {}", shipment.getId());
        calculateFreight(shipment);
        validateShipment(shipment);
        log.info("Completed: {}", shipment.getId());
    }
    
    private void calculateFreight(Shipment s) { }
    private void validateShipment(Shipment s) { }
}

// 사용
ShipmentProcessorThread t = new ShipmentProcessorThread(shipment);
t.start();

1.6 자기 점검 답변

Thread 클래스 상속 방법은?

:
1. 패턴:

  • extends Thread
  • run() 오버라이드
  • start() 호출
  1. 생성자:

    • super("name")
    • 필드 초기화
  2. 변형:

    • 익명 클래스
    • 람다 (Runnable)
  3. 실무:

    • Executor 권장 (Phase 7)

2️⃣ run() 오버라이드

2.1 run() 의 역할

run() 메서드:

  스레드가 실행할 작업을 정의.
  Thread 상속 시 오버라이드.

기본 구현 (Thread.run):
  - Runnable target 이 있으면 실행
  - 없으면 아무것도 안 함

2.2 Thread.run() 의 기본

// Thread 클래스의 run (기본)
public class Thread {
    private Runnable target;
    
    @Override
    public void run() {
        if (target != null) {
            target.run();   // Runnable 있으면 실행
        }
        // 없으면 빈 메서드
    }
}

// 상속 시 오버라이드
class MyThread extends Thread {
    @Override
    public void run() {
        // 작업 정의
    }
}

2.3 run() 의 시그니처

// run() 의 제약
@Override
public void run() {   // void 반환, 매개변수 없음
    // 작업
}

// 제약:
// - void 반환 (결과 X)
// - 매개변수 없음
// - checked exception 던질 수 없음 (Runnable 의 run)

// 결과나 예외가 필요하면:
// - Callable + Future (Phase 7)

2.4 run() 의 예외 처리

class WorkerThread extends Thread {
    @Override
    public void run() {
        try {
            doWork();
        } catch (Exception e) {
            // run 안에서 처리 (밖으로 전파 X)
            log.error("Work failed", e);
        }
        // checked exception 던질 수 없음
        // throws 불가
    }
    
    private void doWork() throws Exception {
        // ...
    }
}

2.5 UncaughtExceptionHandler

// run 에서 처리 안 한 예외
Thread t = new Thread(() -> {
    throw new RuntimeException("Oops");   // 처리 안 함
});

// 핸들러 등록
t.setUncaughtExceptionHandler((thread, throwable) -> {
    log.error("Uncaught in {}: {}", thread.getName(), throwable.getMessage());
});

t.start();
// 예외 발생 시 핸들러 호출
// 스레드는 TERMINATED

2.6 ILIC 의 맥락

public class ShipmentWorker extends Thread {
    
    private final Shipment shipment;
    private final ShipmentService service;
    
    public ShipmentWorker(Shipment shipment, ShipmentService service) {
        super("worker-" + shipment.getId());
        this.shipment = shipment;
        this.service = service;
        
        // 예외 핸들러
        setUncaughtExceptionHandler((t, e) -> 
            log.error("Worker {} failed", t.getName(), e));
    }
    
    @Override
    public void run() {
        try {
            service.process(shipment);
        } catch (Exception e) {
            log.error("Processing failed for {}", shipment.getId(), e);
            // checked exception 못 던지니 여기서 처리
        }
    }
}

2.7 자기 점검 답변

run() 오버라이드의 역할은?

:
1. 역할:

  • 스레드 작업 정의
  • 오버라이드
  1. 기본 구현:

    • Runnable target 실행
    • 없으면 빈 메서드
  2. 제약:

    • void 반환
    • 매개변수 없음
    • checked exception X
  3. 예외:

    • run 안에서 처리
    • UncaughtExceptionHandler

3️⃣ start()의 동작

3.1 start() 의 역할

start() 메서드:

  새 스레드를 생성하고 시작.

동작:
  1. 스레드 상태 확인 (NEW 인지)
  2. OS 레벨 스레드 생성
  3. 상태 NEW → RUNNABLE
  4. 새 스레드가 run() 실행

3.2 start() 의 내부

// start() 의 개념적 동작
public synchronized void start() {
    if (threadStatus != 0) {   // NEW 아니면
        throw new IllegalThreadStateException();
    }
    
    // OS 스레드 생성 (네이티브)
    start0();   // 네이티브 메서드
    // 새 스레드가 run() 호출
}

private native void start0();

3.3 start() 후 흐름

public void demonstrateStart() {
    Thread t = new MyThread();   // NEW
    
    t.start();   // 새 스레드 생성 + run() 실행
    // 현재 스레드: 계속 진행 (이 줄 다음)
    // 새 스레드: run() 실행 (병렬)
    
    System.out.println("Main continues");   // 즉시 (run 과 병렬)
}

3.4 시각화

start() 의 동작:

현재 스레드 (main):
  t.start() 호출
    ↓ (OS 스레드 생성)
  ━━━━━━━━━━━━━━━━━━ (계속 진행)

새 스레드 (t):
         ┌──────────────
         │ run() 실행 (병렬)
         └──────────────

  → 두 스레드 동시 진행

3.5 start() 의 비동기성

public void asyncNature() {
    Thread t = new Thread(() -> {
        Thread.sleep(1000);   // 1초 작업
        System.out.println("Worker done");
    });
    
    t.start();   // 즉시 반환 (1초 안 기다림)
    System.out.println("After start");   // 바로 출력
    
    // 출력 순서:
    // "After start"  (즉시)
    // "Worker done"  (1초 후)
    
    // start() 는 비동기 (run 완료 안 기다림)
}

3.6 ILIC 의 맥락

public class ShipmentParallelProcessor {
    
    public void processInParallel(List<Shipment> shipments) {
        List<Thread> workers = new ArrayList<>();
        
        for (Shipment s : shipments) {
            Thread worker = new ShipmentWorker(s, service);
            worker.start();   // 각각 새 스레드 (병렬 시작)
            workers.add(worker);
        }
        
        // 모든 워커 병렬 진행
        // main 은 계속 (start 비동기)
        
        // 완료 대기 (join — Unit 3.5)
        for (Thread worker : workers) {
            try {
                worker.join();   // 완료까지 대기
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

3.7 자기 점검 답변

start()의 동작은?

:
1. 역할:

  • 새 스레드 생성 + 시작
  1. 동작:

    • 상태 확인 (NEW)
    • OS 스레드 생성
    • NEW → RUNNABLE
    • run() 실행
  2. 비동기:

    • 즉시 반환
    • run 완료 안 기다림
  3. 흐름:

    • 현재 + 새 스레드 병렬

4️⃣ run() 직접 호출 vs start()

4.1 핵심 차이

run() 직접 호출 vs start():

start():
  - 새 스레드 생성
  - 새 스레드가 run() 실행
  - 멀티스레드

run() 직접:
  - 새 스레드 X
  - 현재 스레드가 run() 실행
  - 일반 메서드 호출

4.2 코드 비교

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("run by: " + Thread.currentThread().getName());
    }
}

public void compare() {
    MyThread t = new MyThread();
    
    // start() — 새 스레드
    t.start();
    // 출력: "run by: Thread-0" (새 스레드)
    
    // run() — 현재 스레드
    t.run();
    // 출력: "run by: main" (현재 스레드!)
}

4.3 시각화

start():
  main 스레드: t.start()
                  ↓ (새 스레드 생성)
  새 스레드:      run() 실행
  main 스레드: 계속 (병렬)

run() 직접:
  main 스레드: t.run()
                  ↓
               run() 실행 (main 에서!)
                  ↓ (완료까지 main 블록)
               다음 코드

4.4 실행 스레드 확인

public void whichThread() {
    Thread t = new Thread(() -> {
        System.out.println("Running on: " + 
            Thread.currentThread().getName());
    });
    
    System.out.println("Main: " + Thread.currentThread().getName());
    
    t.start();   // "Running on: Thread-0"
    
    Thread.sleep(100);
    
    t2.run();    // "Running on: main" (run 직접)
}

4.5 흔한 실수

// ❌ 흔한 실수 — run() 호출
Thread t = new Thread(() -> doWork());
t.run();   // 새 스레드 X, main 에서 실행
// "왜 병렬이 안 되지?" → run() 썼기 때문

// ✓ 올바름 — start()
t.start();   // 새 스레드

4.6 ILIC 의 맥락

public class StartVsRunExample {
    
    public void parallelProcessing(List<Shipment> shipments) {
        for (Shipment s : shipments) {
            Thread worker = new Thread(() -> process(s));
            
            // ✓ 병렬
            worker.start();   // 각각 새 스레드
            
            // ❌ 순차 (실수)
            // worker.run();   // main 에서 순차 실행
        }
    }
    
    private void process(Shipment s) {
        log.info("Processing {} on {}", 
            s.getId(), Thread.currentThread().getName());
    }
}

// start(): 여러 스레드 (Thread-0, Thread-1, ...)
// run(): 모두 main 에서 순차

4.7 자기 점검 답변

run() 직접 호출 vs start()는?

:
1. start():

  • 새 스레드
  • 새 스레드가 run
  • 멀티스레드
  1. run() 직접:

    • 새 스레드 X
    • 현재 스레드
    • 일반 메서드
  2. 확인:

    • currentThread().getName()
    • start: Thread-0
    • run: main
  3. 실수:

    • run() 쓰면 병렬 X

5️⃣ 왜 run() 직접 호출은 새 스레드가 없나

5.1 핵심 이유

run() 직접 호출 = 일반 메서드 호출:

  run() 은 그냥 메서드.
  메서드 호출은 현재 스레드에서 실행.

  새 스레드 생성은 start() 의 역할.
  - start() 가 OS 스레드 생성
  - run() 은 작업 정의만

5.2 메서드 호출의 본질

메서드 호출:

  obj.method();
  - 현재 스레드의 스택에 프레임
  - 현재 스레드에서 실행
  - 새 스레드 X

run() 도 메서드:
  t.run();
  - 그냥 메서드 호출
  - 현재 스레드 스택
  - 현재 스레드에서 실행

5.3 start() 의 특별함

start() 가 새 스레드 만드는 이유:

  start() 내부:
    - start0() 네이티브 호출
    - OS 에 새 스레드 요청
    - 새 스레드가 run() 호출

  run() 직접:
    - 네이티브 호출 X
    - 그냥 메서드 실행
    - 새 스레드 X

5.4 시각화

start() 내부:
  start()
    → start0() (네이티브)
      → OS: 새 스레드 생성
        → 새 스레드가 run() 실행

run() 직접:
  run()
    → 메서드 본문 실행 (현재 스레드)
    (네이티브 X, OS 스레드 X)

5.5 스택 관점

start() — 두 개의 스택:

main 스택:
  start() 프레임
  (start 후 계속)

새 스레드 스택:
  run() 프레임
  (독립적)

run() 직접 — 하나의 스택:

main 스택:
  run() 프레임 (main 스택에)
  (run 완료까지 main 블록)

5.6 ILIC 의 맥락

public class WhyRunNoNewThread {
    
    public void demonstrate() {
        Thread t = new Thread(() -> {
            String current = Thread.currentThread().getName();
            log.info("Running on: {}", current);
            
            // 스택 깊이 확인
            log.info("Stack depth: {}", 
                Thread.currentThread().getStackTrace().length);
        });
        
        // start() — 새 스레드 스택
        t.start();
        // "Running on: Thread-X"
        // 독립 스택
        
        Thread.sleep(100);
        
        // run() — main 스택
        Thread t2 = new Thread(() -> 
            log.info("Run on: {}", Thread.currentThread().getName()));
        t2.run();
        // "Run on: main"
        // main 스택에서 실행
    }
}

5.7 자기 점검 답변

왜 run() 직접 호출은 새 스레드가 없나?

:
1. 핵심:

  • run() 은 그냥 메서드
  • 메서드 호출 = 현재 스레드
  1. 메서드 본질:

    • 현재 스레드 스택
    • 현재 스레드 실행
  2. start() 특별함:

    • start0() 네이티브
    • OS 스레드 생성
    • 새 스레드가 run
  3. 스택:

    • start: 두 스택 (병렬)
    • run: 한 스택 (현재)

6️⃣ start() 두 번 호출

6.1 IllegalThreadStateException

Thread t = new Thread(() -> doWork());

t.start();   // OK (NEW → RUNNABLE)
t.start();   // ❌ IllegalThreadStateException
// 이미 시작된 스레드 재시작 불가

6.2 왜 두 번 안 되나

start() 두 번 안 되는 이유:

  스레드는 일회용.
  - NEW 에서만 start() 가능
  - 한 번 시작하면 NEW 아님
  - TERMINATED 후도 NEW 아님

  start() 내부:
    if (threadStatus != 0) {   // NEW 아니면
        throw new IllegalThreadStateException();
    }

6.3 상태 확인

Thread t = new Thread(() -> doWork());

System.out.println(t.getState());   // NEW
t.start();
System.out.println(t.getState());   // RUNNABLE (NEW 아님)

t.start();   // ❌ NEW 아니므로 예외

// TERMINATED 후도:
t.join();
System.out.println(t.getState());   // TERMINATED
t.start();   // ❌ 여전히 NEW 아니므로 예외

6.4 재실행이 필요하면

// ❌ 재시작 불가
Thread t = new Thread(task);
t.start();
t.join();
// t.start();   // 예외

// ✓ 새 스레드 생성
Thread t1 = new Thread(task);
t1.start();
t1.join();

Thread t2 = new Thread(task);   // 새 인스턴스
t2.start();   // OK

// ✓ 또는 스레드 풀 (재사용)
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(task);
executor.submit(task);   // 같은 풀 스레드 재사용

6.5 스레드 풀의 재사용

// 스레드 풀 — 스레드 재사용 (Phase 7)
ExecutorService executor = Executors.newFixedThreadPool(2);

// 같은 풀의 스레드가 여러 작업 처리
executor.submit(() -> task1());
executor.submit(() -> task2());
executor.submit(() -> task3());
// 2 스레드가 3 작업 (재사용)

// 풀의 스레드는 작업 후 종료 X
// 다음 작업 대기
// → start() 두 번 문제 회피

6.6 ILIC 의 맥락

public class ThreadReuseExample {
    
    // ❌ 재시작 시도 (실수)
    public void badRetry(Shipment shipment) {
        Thread worker = new ShipmentWorker(shipment, service);
        worker.start();
        try {
            worker.join();
            if (failed) {
                worker.start();   // ❌ IllegalThreadStateException
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    // ✓ 재시도 — 새 스레드
    public void goodRetry(Shipment shipment) throws InterruptedException {
        Thread worker = new ShipmentWorker(shipment, service);
        worker.start();
        worker.join();
        
        if (failed) {
            Thread retry = new ShipmentWorker(shipment, service);   // 새 인스턴스
            retry.start();
        }
    }
    
    // ✓✓ 스레드 풀 (권장)
    private final ExecutorService executor = Executors.newFixedThreadPool(4);
    
    public void poolBased(Shipment shipment) {
        executor.submit(() -> service.process(shipment));
        executor.submit(() -> service.process(shipment));   // 재사용
    }
}

6.7 자기 점검 답변

start()를 두 번 호출하면?

:
1. 결과:

  • IllegalThreadStateException
  1. 이유:

    • 스레드 일회용
    • NEW 에서만 start
    • 시작 후 NEW 아님
  2. 상태:

    • start 후 RUNNABLE
    • 종료 후 TERMINATED
    • 둘 다 재시작 X
  3. 해결:

    • 새 인스턴스
    • 스레드 풀 (재사용)

7️⃣ 상태로 설명 (start vs run)

7.1 start() 의 상태 전이

start() 의 상태:

  Thread 객체 t (NEW)
    ↓ t.start()
  t (RUNNABLE)
    - 새 스레드 생성
    - 새 스레드가 run 실행

  호출 스레드 (main): 영향 없음 (계속 RUNNABLE)

7.2 run() 직접의 상태

run() 직접 호출의 상태:

  Thread 객체 t (NEW 그대로!)
    ↓ t.run()
  t (NEW 유지)
    - run() 은 main 에서 실행
    - t 의 상태 변화 X

  호출 스레드 (main): run 실행 중 (RUNNABLE)

7.3 상태 확인 코드

public void stateComparison() throws InterruptedException {
    // start()
    Thread t1 = new Thread(() -> {
        try { Thread.sleep(100); } catch (Exception e) {}
    });
    System.out.println("Before start: " + t1.getState());   // NEW
    t1.start();
    System.out.println("After start: " + t1.getState());    // RUNNABLE
    
    // run()
    Thread t2 = new Thread(() -> {
        // run 직접
    });
    System.out.println("Before run: " + t2.getState());   // NEW
    t2.run();   // main 에서 실행
    System.out.println("After run: " + t2.getState());    // NEW (변화 X!)
    // t2 는 시작 안 됨 (run 만 main 에서 호출)
}

7.4 시각화

start() 상태:
  t: NEW → RUNNABLE → ... → TERMINATED
  (t 가 실제 스레드로 동작)

run() 직접 상태:
  t: NEW → NEW → NEW (변화 없음!)
  (t 는 시작 안 함, main 이 run 실행)
  main: run 실행 (main 의 작업처럼)

7.5 핵심 정리

상태로 본 차이:

start():
  - t 가 NEW → RUNNABLE
  - t 가 실제 스레드

run() 직접:
  - t 는 NEW 유지
  - run 은 main 에서 (메서드 호출)
  - t 는 스레드로 동작 안 함

핵심:
  - start: 상태 전이 O
  - run: 상태 전이 X

7.6 ILIC 의 맥락

public class StateBasedExplanation {
    
    public void explain() throws InterruptedException {
        ShipmentWorker worker = new ShipmentWorker(shipment, service);
        
        // start() — 상태 전이
        log.info("Before: {}", worker.getState());   // NEW
        worker.start();
        log.info("After start: {}", worker.getState());   // RUNNABLE
        worker.join();
        log.info("After join: {}", worker.getState());   // TERMINATED
        
        // run() — 상태 전이 X (새 인스턴스로)
        ShipmentWorker worker2 = new ShipmentWorker(shipment, service);
        log.info("Before run: {}", worker2.getState());   // NEW
        worker2.run();   // main 에서 실행
        log.info("After run: {}", worker2.getState());   // NEW (변화 X!)
    }
}

7.7 자기 점검 답변

start() vs run()을 상태로 설명하면?

:
1. start():

  • NEW → RUNNABLE
  • t 가 실제 스레드
  • 상태 전이 O
  1. run() 직접:

    • NEW 유지 (변화 X)
    • main 에서 실행
    • 상태 전이 X
  2. 핵심:

    • start: t 가 스레드로
    • run: t 는 그대로, main 이 실행

8️⃣ Thread의 주요 메서드와 한계

8.1 주요 메서드

Thread t = new Thread(task);

// 생애주기
t.start();              // 시작
t.join();               // 종료 대기
t.interrupt();          // 인터럽트
t.isAlive();            // 살아있는지

// 정보
t.getName();            // 이름
t.setName("name");      // 이름 설정
t.getState();           // 상태
t.getId() / threadId(); // ID
t.getPriority();        // 우선순위
t.setPriority(5);       // 우선순위 설정

// 데몬
t.setDaemon(true);      // 데몬 설정
t.isDaemon();           // 데몬 여부

// 정적
Thread.currentThread(); // 현재 스레드
Thread.sleep(1000);     // 대기
Thread.yield();         // 양보

8.2 currentThread()

// 현재 실행 스레드
Thread current = Thread.currentThread();
System.out.println(current.getName());   // 현재 스레드 이름

// 활용
public void logCurrentThread() {
    String name = Thread.currentThread().getName();
    log.info("Executing on: {}", name);
}

8.3 sleep()

// 현재 스레드 대기 (static)
Thread.sleep(1000);   // 1초 (TIMED_WAITING)

// InterruptedException 처리
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();   // 플래그 복원
}

// 주의:
// - 락 반납 X (wait 와 다름)
// - static (현재 스레드)

8.4 Thread 상속의 한계

Thread 상속의 한계:

1. 단일 상속 제약
   - 자바는 단일 상속
   - Thread 상속 시 다른 클래스 X

2. 작업 + 제어 결합
   - 작업 (run) 과 스레드 제어 섞임
   - 객체지향적 X

3. 재사용 어려움
   - 작업과 스레드 분리 X
   - 같은 작업 여러 스레드 어려움

→ Runnable 이 대안 (다음 Unit)

8.5 단일 상속 문제

// ❌ Thread 상속 시 다른 클래스 상속 불가
class ShipmentProcessor extends BaseProcessor {  // 이미 상속
    // extends Thread 불가 (단일 상속)
}

// 해결: Runnable 구현
class ShipmentProcessor extends BaseProcessor 
        implements Runnable {   // 인터페이스는 다중 가능
    @Override
    public void run() {
        // 작업
    }
}
new Thread(new ShipmentProcessor()).start();

8.6 ILIC 의 맥락

// Thread 상속 (한계 있음)
public class ShipmentThreadInherit extends Thread {
    // BaseService 상속 불가 (이미 Thread)
    @Override
    public void run() { }
}

// Runnable 구현 (권장 — 다음 Unit)
public class ShipmentRunnable extends BaseService 
        implements Runnable {   // 다른 클래스 + Runnable
    @Override
    public void run() {
        process();   // BaseService 의 메서드 활용
    }
}

// 사용
new Thread(new ShipmentRunnable()).start();

// 실무: Executor (Phase 7)
executor.submit(new ShipmentRunnable());

8.7 자기 점검 답변

Thread의 주요 메서드와 한계는?

:
1. 주요 메서드:

  • start, join, interrupt
  • getName, getState, setPriority
  • setDaemon
  • currentThread, sleep, yield (static)
  1. 한계:

    • 단일 상속 제약
    • 작업 + 제어 결합
    • 재사용 어려움
  2. 대안:

    • Runnable (다음 Unit)
    • Executor (Phase 7)

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
Thread 상속?extends Thread + run 오버라이드
run() 역할?스레드 작업 정의
start() 동작?새 스레드 생성 + run 실행
run() 직접 호출?새 스레드 X, 현재 스레드
왜 새 스레드 없나?run 은 그냥 메서드
start() 두 번?IllegalThreadStateException
start vs run 상태?RUNNABLE vs NEW 유지
start() 비동기?즉시 반환 (run 대기 X)
Thread 한계?단일 상속, 결합
run 예외?checked 못 던짐, 안에서 처리

9.2 자기 점검 체크리스트

Thread 상속

  • extends Thread
  • run() 오버라이드
  • 생성자

run()

  • 작업 정의
  • void 반환
  • checked exception X

start()

  • 새 스레드
  • 비동기
  • NEW → RUNNABLE

run() vs start()

  • 새 스레드 유무
  • 실행 스레드
  • 상태 변화

두 번 호출

  • IllegalThreadStateException
  • 일회용
  • 새 인스턴스/풀

한계

  • 단일 상속
  • 결합
  • Runnable 대안

9.3 추가 심화 질문

Q1: Thread 가 Runnable 을 구현?

답:

  • Thread implements Runnable
  • run() 메서드 보유
  • Thread 상속 시 run 오버라이드
  • 또는 Runnable 전달

Q2: setDaemon 은 언제?

답:

  • start() 전에만
  • start() 후 호출 시 IllegalThreadStateException
  • 데몬 여부는 시작 전 결정 (Unit 3.4)

Q3: 스레드 이름의 기본값?

답:

  • "Thread-0", "Thread-1", ... (자동 증가)
  • 생성자로 지정 가능
  • setName() 으로 변경

Q4: run() 안에서 currentThread()?

답:

  • start(): 새 스레드 반환 (Thread-X)
  • run() 직접: 현재 스레드 (main)
  • 디버깅에 유용

Q5: Thread 우선순위?

답:

  • 1 (MIN) ~ 10 (MAX), 5 (NORM)
  • setPriority()
  • OS 에 힌트일 뿐
  • 의존하면 안 됨

🎯 핵심 요약 — 3줄 정리

1. Thread 상속

  • extends Thread + run() 오버라이드
  • start() 로 새 스레드

2. start() vs run()

  • start(): 새 스레드 (RUNNABLE)
  • run() 직접: 현재 스레드 (NEW 유지)

3. 주의와 한계

  • start() 두 번 → IllegalThreadStateException
  • 단일 상속 제약 → Runnable 대안

📚 다음으로...

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

이번 Unit에서 Thread 상속을 봤다면, 다음은 Runnable 인터페이스 (권장 방식).

  • Thread 상속의 한계
  • Runnable 의 장점 3가지
  • 람다로 Runnable
  • 작업과 스레드 분리

Phase 3 진행 상황

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

4주차 누적 진행

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

총: 9/35 Unit
profile
Software Developer

0개의 댓글