From e9c9506afe9a1eb2a3a3e1219274ba0d66f420f5 Mon Sep 17 00:00:00 2001 From: Jihyun-Choi <034179@naver.com> Date: Wed, 6 Aug 2025 23:49:45 +0900 Subject: [PATCH] =?UTF-8?q?5=EC=A3=BC=EC=B0=A8:=20ch6=20-=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=8B=A4=ED=96=89(=EC=B5=9C=EC=A7=80=ED=98=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/ch04(with Python).md | 0 study/ch06.md | 661 +++++++++++++++++++++++++++++++++++++ 2 files changed, 661 insertions(+) create mode 100644 study/ch04(with Python).md create mode 100644 study/ch06.md diff --git a/study/ch04(with Python).md b/study/ch04(with Python).md new file mode 100644 index 0000000..e69de29 diff --git a/study/ch06.md b/study/ch06.md new file mode 100644 index 0000000..1661e55 --- /dev/null +++ b/study/ch06.md @@ -0,0 +1,661 @@ +**목차** + +- 6.1 스레드에서 작업 실행 +- 6.2 Executor 프레임웍 +- 6.3 병렬로 처리할 만한 작업 + + +## 1. 스레드에서 작업 실행 + +> **작업(Task)의 개념** +> +- 작업이란 **추상적이면서도 명확하게 구분된 업무 단위** +- **완전히 독립적**인 동작이어야 하며, 다른 작업의 상태, 결과, 부수효과 등에 영향을 받지 않아야 병렬성 보장 가능 +- 작업의 범위를 어디까지로 정의할 것인가가 중요함. + - 가장 간단한 방법: 클라이언트 요청 단위 + +### 1.1. 작업을 순차적으로 실행 + +- 가장 단순한 실행 방법으로, 작업 목록을 순차적으로 단일 스레드에서 처리 + +```java +class SingleThreadWebServer { + public static void main(String[] args) throws IOException { + ServerSocket socket = new ServerSocket(80); + while (true){ + Socket connection = socket.accept(); + handleRequest(connection); + } + } +} +``` + +- 문제점 + - **한 번에 하나의 요청만 처리** → **낮은 처리량** + - 처리 중인 요청이 끝날 때까지 **다른 요청 대기** + - **I/O 작업** 중에도 CPU가 **놀게 됨** + - 서버 하드웨어 자원을 **비효율적으로 사용** + - 처리 시간 + 응답 시간 모두 **지연** + - **실제 서버 환경**에서는 거의 사용되지 않음 + +### 1.2. 작업마다 스레드를 직접 생성 + +- 요청마다 **새로운 스레드** 생성하며, 메인 스레드는 다음 요청을 **빠르게 수신** 가능 +- 각 작업은 **별도 스레드에서 병렬 처리** + +```java +class ThreadPerTaskWebServer { + public static void main(String[] args) throws IOException { + ServerSocket socket = new ServerSocket(80); + while (true) { + final Socket conn = socket.accept(); + Runnable task = new Runnable() { + public void run() { + handleRequest(conn); + } + }; + new Thread(task).start(); + } + } +} +``` + +- 장점 + - 응답 속도 향상 (메인 스레드가 빠르게 반복) + - 병렬 처리 가능 → 동시에 여러 요청 처리 + - CPU 다중 코어 활용 가능 + - I/O, 락 대기 등으로 인한 지연 최소화 + - 순차적 실행 방식에 비해 훨씬 높은 처리량 +- 주의사항 + - **스레드 안정성** 필요 (동시성 문제 발생 가능) + - 전제: `요청 처리 속도 > 요청 도착 속도` + +### 1.3. 스레드를 많이 생성할 때의 문제점 + +**라이프사이클 오버헤드** + +- 스레드 생성/소멸 자체에 **자원과 시간 소모** +- 요청이 **간단하고 빈번**할 경우, 스레드 생성 시간이 **병목 요인**이 될 수 있음 + +**자원 낭비** + +- 실행 중인 스레드는 **메모리와 CPU 자원** 소비 +- **대기 상태 스레드**가 많을수록 메모리 소비 ↑, **GC 부하 증가,** CPU 자원 간 **경쟁 심화** +- CPU 코어 수보다 스레드 수가 많으면 **성능 개선 X**, 오히려 **악화 가능성** + +**안정성 문제** + +- 시스템마다 **생성 가능한 스레드 수는 제한적** +- 초과 시 `OutOfMemoryError` 발생 가능 +- 예외 복구 어려움 → **서버 다운으로 이어질 수 있음** +- 특히 32비트 시스템은 **스택 공간 제약**으로 더 민감 + +**결론** + +- **스레드 수 제한**은 필수 +- 제한된 스레드 내에서 **부하 테스트 필요** +- 과부하 상황에서도 **점진적 성능 저하**가 일어나야지, **즉시 실패**해서는 안 됨 + +## 2. Executor 프레임웍 + +**기본 개념** + +- `작업(Task)`은 **논리적인 업무 단위**, `스레드`는 이를 **비동기 실행**하는 수단 +- **Executor**는 작업 실행을 위한 **표준 인터페이스** + +```java +public interface Executor { + void execute(Runnable command); +} +``` + +> **주요 특징** +> +- `Thread`보다 **높은 추상화 수준** → 사용 편의성↑ +- **작업 등록**과 **실행**을 분리 (task submission vs execution) +- **Runnable** 형태로 작업 정의 +- **비동기적 실행 정책**을 유연하게 지원 +- Executor는 **프로듀서-컨슈머 패턴** 구조 + - **프로듀서**: 작업을 생성하고 제출 + - **컨슈머**: 작업을 실제로 처리하는 스레드 + +**스레드 풀 & 자원 관리** + +- 스레드 풀(Thread Pool)은 Executor의 핵심 구성 요소 +- **자원 고갈 방지**: bounded queue + 제한된 스레드 수로 통제 가능 +- 작업 실행 시 메모리/CPU 자원 **효율적으로 관리** + +#### 더 알아보기: **Executor 비동기 프레임워크와 병렬 처리** + +자바 병렬 프로그래밍에서 "비동기"란 단지 **작업이 다른 스레드에서 실행되는 것**을 의미 + +즉, 메인 스레드가 직접 작업을 실행하지 않고, 다른 스레드에게 위임하고, 즉시 다음 로직으로 넘어갈 수 있는 구조를 말한다. + +비동기 실행을 직접 Thread를 new해서 만들기보단, Executor를 통해 스레드 풀에서 관리되는 스레드에 위임하는 것이 더 효과적이다. + +- **스레드 재사용** → 자원 효율적 +- **작업 큐**와 **스레드 개수 제어** → 과부하 방지 +- **비동기 처리 → 병렬 처리** 구조 자연스럽게 구성 + +⇒ 비동기 실행은 병렬성 확보 수단이고, Executor는 이를 효율적/안정적으로 지원하는 도구 + +- [Java - 실행자(Executor)](https://cabi.oopy.io/38a5736a-8e72-48cc-8882-70d8c1364f5c) + + +#### 더 알아보기: **Runnable?** +> **자바에서의 `Runnable` : 병렬/비동기 처리용 실행 단위** +> + +```java +Runnable task = () -> { + System.out.println("Hello from another thread!"); +}; +``` + +- Runnable은 자바의 함수형 인터페이스 +- 오직 하나의 메서드 `run()`을 가지며, 실행할 "작업(로직)"을 담는 단위 +- `Thread`나 `Executor`에게 전달되어 비동기 작업 실행에 사용 + +> **LangChain에서의 `Runnable` : LLM 컴포넌트 체이닝 단위** +> + +LangChain의 Runnable은 OpenAI가 만든 LCEL (LangChain Expression Language)의 구성 요소 중 하나로, "체이닝 가능한 컴포넌트"를 표현하는 프로그래머블 객체 인터페이스를 의미 + +```java +from langchain_core.runnables import Runnable + +class MyChain(Runnable): + def invoke(self, input): + return some_processing(input) +``` + +- LangChain에서의 `Runnable`은 LLM, PromptTemplate, Tool 등 모듈들을 체이닝하는 기반 인터페이스 +- `invoke()`를 통해 실행됨 (자바의 `run()`과는 전혀 무관) +- 데이터 흐름과 컴포넌트 연결 중심 + + +### 2.1. 예제: Executor를 사용한 웹서버 + +> **스레드 풀 기반 웹서버 예제** +> + +```java +Executor exec = Executors.newFixedThreadPool(NTHREADS); +exec.execute(() -> handleRequest(conn)); +``` + +- **요청 등록**과 **작업 실행**이 Executor로 분리됨 +- Executor 설정만 바꾸면 서버 동작 특성 쉽게 변경 가능 +- 대부분 **초기 실행 시점**에 설정값 지정 + +**구조적 장점** + +- 다양한 Executor 구현으로 **전략 변경 용이** +- **스레드 풀 기반 병렬 처리 구조** 간편하게 구현 가능 + +> **Executor의 대체 구현 예시** +> + +1. 작업마다 스레드 생성 + +- 요청마다 **새로운 스레드 생성** +- 기존 구조 유지하면서 **동작 방식만 변경 가능** + +```java +public class ThreadPerTaskExecutor implements Executor { + public void execute(Runnable r) { + new Thread(r).start(); + } +} +``` + +2. 요청 스레드에서 직접 실행 + +- **등록한 스레드에서 직접 처리** +- 사실상 **순차 처리 구조로 전환** + +```java +public class WithinThreadExecutor implements Executor { + public void execute(Runnable r) { + r.run(); + } +} +``` + +Executor를 사용하면 **웹서버의 실행 정책을 유연하게 구성**할 수 있으며, + +인터페이스만 유지하면 다양한 동작 방식(스레드풀, 스레드 생성, 순차 처리)으로 쉽게 전환 가능함. + +### 2.2 실행 정책 + +> **실행 정책의 의미** +> +- 무엇을, 어디서, 언제, 어떻게 실행할지 결정하는 자원 관리 전략 +- 작업 실행 방식의 유연성과 확장성을 높여줌 + +> **주요 설정 항목** +> +- 어떤 스레드에서 작업 실행? +- 어떤 순서로 실행? (FIFO, LIFO, 우선순위 등) +- 동시에 몇 개의 작업을 병렬 실행? +- 큐에서 최대 대기 작업 수는? +- 부하로 인해 작업 거절 시: + - 어떤 작업을 **희생시킬지**? + - **요청자에게 어떻게 통보할지**? +- 작업 실행 전/후에 처리할 동작은? + +> **실행 정책의 활용** +> +- **작업 등록과 실행을 분리**하면 정책 변경이 쉬움 +- `new Thread(runnable).start()` → **Executor로 리팩토링** 권장 +- **하드웨어/자원 상황**에 따라 실행 정책을 조정 가능 +- 성능/응답속도 요구사항에 따라 정책 최적화 필요 + +### 2.3. 스레드 풀 + +- 동일한 형태의 작업 스레드를 풀(pool)로 관리 +- **재사용 가능**한 스레드로 불필요한 생성/소멸 방지 +- 작업 처리용 스레드는 **작업 큐**에서 작업을 꺼내 반복 처리 + +**장점** + +- 스레드 생성 비용 감소 → **자원 절약** +- 요청 수신 즉시 처리 가능 → **반응 속도 향상** +- 적절한 풀 크기로 **CPU/메모리 사용 최적화** +- 부하 시에도 **성능이 점진적으로 저하**됨 (안정적) + +> **Executors 제공 스레드 풀 종류** +> + +| 메서드 | 특징 | +| --- | --- | +| `newFixedThreadPool(n)` | 고정된 개수의 스레드 유지. 초과 작업은 큐에 대기 | +| `newCachedThreadPool()` | 필요한 만큼 스레드 생성. 유휴 스레드는 종료됨. 제한 없음 | +| `newSingleThreadExecutor()` | 단일 스레드. 작업 순차 처리. 예외 발생 시 스레드 재생성 | +| `newScheduledThreadPool(n)` | **지연 실행**, **주기 실행** 지원. `Timer` 대체 가능 | +- 작업별로 스레드를 생성하는 전략(Thread-per-task) → 풀을 기반 전략(Pool-based )으로 전환 시 + - **메모리 부족으로 인한 서버 다운 방지** + - 과도한 동시성 → **자원 경쟁 방지** + - 성능 저하 시에도 **완만하게 감소** +- `Executors.*` 메서드는 내부적으로 `ThreadPoolExecutor` 사용 +- `ThreadPoolExecutor`는 설정을 커스터마이징할 수 있음 (8장에서 자세히 다룸) +- `Executor`를 사용하면 튜닝, 로깅, 모니터링, 예외 처리 등 관리가 용이해짐 + +### 2.4. Executor 동작 주기 + +- Executor는 비동기로 작업을 실행하므로 작업 상태 파악이 어려움 +- Executor를 정상 종료하지 않으면 JVM이 종료되지 않고 대기함 + +> **ExecutorService 인터페이스** +> + +```java +public interface ExecutorService extends Executor { + void shutdown(); + List shutdownNow(); + boolean isShutdown(); + boolean isTerminated(); + boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; +} + +``` + +- `ExecutorService`는 **동작 주기 관리 기능**을 제공 +- **동작 상태** + - **running:** 생성 직후 상태, 작업 등록 가능 + - **shutting down:** `shutdown()` 호출 시 진입, 새로운 작업 등록 불가, 이전 작업은 완료 + - **terminated:** 모든 작업 완료 후 진입, 완전 종료 상태 +- 종료 방식 + - `shutdown()`: 안전한 종료, 등록된 작업은 끝까지 실행 + - `shutdownNow()`: 강제 종료, 실행 중인 작업은 가능한 취소, 대기 중인 작업은 실행 X + - 종료 후 작업 등록 시 → RejectedExecutionException 발생 (`RejectedExecutionHandler`에 의해 처리) +- 종료 확인/대기 + - `awaitTermination(timeout, unit)`: 종료 완료까지 **대기** + - `isTerminated()`: 종료 상태인지 **주기적으로 확인** + +> **예제: LifecycleWebServer** +> + +```java +while (!exec.isShutdown()) { + try { + Socket conn = socket.accept(); + exec.execute(() -> handleRequest(conn)); + } catch (RejectedExecutionException e) { + if (!exec.isShutdown()) log("task rejected", e); + } +} +``` + +- 종료 방법: + - `stop()` 메서드 호출 + - 클라이언트로부터 **특정 종료 요청** 수신 + +- **`ExecutorService` 인터페이스 정의** + + ```java + public interface ExecutorService extends Executor { + /** + * Initiates an orderly shutdown in which previously submitted + * tasks are executed, but no new tasks will be accepted. + * Invocation has no additional effect if already shut down. + * + *

This method does not wait for previously submitted tasks to + * complete execution. Use {@link #awaitTermination awaitTermination} + * to do that. + * + * @throws SecurityException if a security manager exists and + * shutting down this ExecutorService may manipulate + * threads that the caller is not permitted to modify + * because it does not hold {@link + * java.lang.RuntimePermission}{@code ("modifyThread")}, + * or the security manager's {@code checkAccess} method + * denies access. + */ + void shutdown(); + + /** + * Attempts to stop all actively executing tasks, halts the + * processing of waiting tasks, and returns a list of the tasks + * that were awaiting execution. + * + *

This method does not wait for actively executing tasks to + * terminate. Use {@link #awaitTermination awaitTermination} to + * do that. + * + *

There are no guarantees beyond best-effort attempts to stop + * processing actively executing tasks. For example, typical + * implementations will cancel via {@link Thread#interrupt}, so any + * task that fails to respond to interrupts may never terminate. + * + * @return list of tasks that never commenced execution + * @throws SecurityException if a security manager exists and + * shutting down this ExecutorService may manipulate + * threads that the caller is not permitted to modify + * because it does not hold {@link + * java.lang.RuntimePermission}{@code ("modifyThread")}, + * or the security manager's {@code checkAccess} method + * denies access. + */ + List shutdownNow(); + + /** + * Returns {@code true} if this executor has been shut down. + * + * @return {@code true} if this executor has been shut down + */ + boolean isShutdown(); + + /** + * Returns {@code true} if all tasks have completed following shut down. + * Note that {@code isTerminated} is never {@code true} unless + * either {@code shutdown} or {@code shutdownNow} was called first. + * + * @return {@code true} if all tasks have completed following shut down + */ + boolean isTerminated(); + + /** + * Blocks until all tasks have completed execution after a shutdown + * request, or the timeout occurs, or the current thread is + * interrupted, whichever happens first. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @return {@code true} if this executor terminated and + * {@code false} if the timeout elapsed before termination + * @throws InterruptedException if interrupted while waiting + */ + boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException; + + ... + + } + ``` + + +### 2.5. 지연 작업, 주기적 작업 + +> **Timer 클래스의 기능** +> +- **지연 작업** 및 **주기적 작업** 실행 가능 +- 자바 기본 라이브러리에 포함 +- **주요 단점** + - **스레드 1개만 사용** + + → 하나의 작업이 오래 걸리면 다른 작업이 **예정 시간에 실행되지 못함** + + - **예외 처리 없음** + + → `TimerTask` 실행 중 예외 발생 시 **Timer 스레드 자체가 종료**됨 + + → 등록된 모든 작업 **취소** 및 **새 작업 등록 불가** + + +> **ScheduledThreadPoolExecutor의 장점** +> +- 여러 스레드를 사용하여 지연/주기 작업 실행 + + → 각각의 작업이 예정 시각을 벗어나지 않도록 조절 + +- 생성 방법: + - `new ScheduledThreadPoolExecutor(...)` + - `Executors.newScheduledThreadPool(...)` + +## 3. 병렬로 처리할 만한 작업 + +- 서버 애플리케이션에서 클라이언트 요청 **한 건 처리 과정도 병렬화** 가능. 특히 **DB 서버** 등에서 일반적 +- 큰 작업을 **작은 단위로 분할**하여 동시에 실행하면, **CPU 활용도 증가**, **응답 시간 감소** + +### 3.1. 예제: 순차적 페이지 렌더링 + +```java +public class SingleThreadRenderer { + void renderPage(CharSequence source) { + renderText(source); + List imageData = new ArrayList<>(); + for (ImageInfo imageInfo : scanForImageInfo(source)) + imageData.add(imageInfo.downloadImage()); + for (ImageData data : imageData) + renderImage(data); + } +} +``` + +- 텍스트는 바로 렌더링, 이미지는 **순차적으로 다운로드 후 렌더링** +- 이미지 다운로드는 **I/O 중심 작업** + + → **CPU를 거의 사용하지 않음**, 대기 시간 길어짐 + +- 이 방식은 CPU를 **비효율적으로 사용**, 사용자에게 **느린 페이지**를 제공 + +### **3.2 결과가 나올 때까지 대기 : Callable과 Future** + +- Runnable의 한계 + - `run()`은 **결과값 반환 불가**, `throws` 사용 불가 + - 결과값은 **공유 자원에 저장**, 예외는 **로그 기록** 정도로 처리 + - **실행만 하고 결과가 필요한 작업**엔 부적합 +- Callable의 특징 + - `call()` 실행 후 **결과 반환 가능** + - **예외 처리도 가능** + - 결과값이 없어도 `Callable` 형태로 표현 가능 + +> **Future** +> +- 작업의 **상태 조회, 결과 조회, 취소** 등을 위한 인터페이스 +- `get()` 호출 시: + - 작업 완료됨 → **즉시 결과 반환** 또는 **예외 던짐** + - 작업 미완료 → **완료 시까지 블로킹** + - 예외 발생 시 → `ExecutionException`, `getCause()`로 원인 확인 + - 작업 취소 시 → `CancellationException` 발생 +- **작업의 생명주기** + - `created → submitted → started → completed` 상태 순으로 진행 + - **한 번 완료된 작업은 상태 되돌릴 수 없음** + - `Future.cancel()`로 **미시작 작업은 언제든 취소 가능** + - 이미 시작된 작업은 **interrupt 지원 여부**에 따라 취소 가능 +- **작업 등록 및 Future 생성** + - `ExecutorService.submit(Runnable/Callable)` → `Future` 리턴 + - `Runnable` 또는 `Callable`을 이용해 **직접 `FutureTask` 생성** 가능 +- `ThreadPoolExecutor` 등에서는 `newTaskFor()`를 오버라이드하여 등록된 작업의 `Future` 생성 방식에 관여할 수 있음 +- **안전한 결과 전달** + - `Runnable`/`Callable`은 실행 스레드로 **안전하게 전달됨** + - `Future`의 결과값도 **get()을 통해 안전하게 공유**됨 + +### **3.3 예제: Future를 사용해 페이지 렌더링** + +- 텍스트 렌더링(CPU) 과 이미지 다운로드(I/O) 를 병렬 처리 +- 이미지 다운로드는 `Callable`로 정의, `Future`로 결과 대기 +- UI/렌더링 흐름을 방해하지 않으면서 백그라운드에서 이미지 처리 + +> **장점** +> +- `Callable` + `Future` 구조로 **비동기 결과 대기 및 오류 대응** 가능 +- `get()` 호출 전 이미지 다운로드가 완료됐을 가능성이 높아 **대기 시간 최소화** +- 전체 페이지 렌더링이 **순차적 방식보다 훨씬 빠름** +- 작업 등록 및 결과 조회는 모두 **스레드 안전하게 공개됨** + +> **예외 처리 (get() 사용 시 반드시 필요)** +> +- `InterruptedException`: 메인 스레드가 인터럽트된 경우 + + → 스레드 상태 복원 + `future.cancel(true)` + +- `ExecutionException`: 작업 중 예외 발생 + + → `getCause()`로 원인 확인 후 적절히 처리 + + +### **3.4 다양한 형태의 작업을 병렬로 처리하는 경우의 단점** + +- 다양한 작업을 병렬 처리하면 **작업 크기 불균형** 발생 가능 +- 스레드 간 **조율 비용** 발생 → 자원 소모 +- **병렬 처리로 인한 성능 이득 > 조율 부하**가 되어야 의미 있음 +- **동일한 작업을 대량으로 재정의**해 병렬 처리할 수 있어야 실제 성능 향상 가능 + +### **3.5 CompletionService: Executor와 BlockingQueue의 연합** + +- Executor + BlockingQueue 기능을 합친 인터페이스 +- 작업 결과가 완료되는 즉시 Future로 꺼내 사용 가능 +- **주요 기능** + - `Callable` 작업을 등록하여 Executor로 실행 + - 완료된 작업은 BlockingQueue에 저장 + - `take()`, `poll()`로 완료된 Future 즉시 획득 가능 + +> **구현 클래스: ExecutorCompletionService** +> + +```java +private class QueueingFuture extends FutureTask { + QueueingFuture(Callable c) { super(c); } + QueueingFuture(Runnable t, V r) { super(t, r); } + + protected void done() { + completionQueue.add(this); + } +} +``` + +- 내부적으로 **BlockingQueue를 생성자에서 초기화** +- 작업 등록 시, `FutureTask`를 상속한 **QueueingFuture**로 감싸 처리 +- 작업 완료 시 `done()` 메소드 호출 → **completionQueue에 추가** + +### **3.6 예제: CompletionService를 활용한 페이지 렌더링** + +- 각 이미지 다운로드 작업을 **Callable로 생성** +- `Executor`를 통해 **병렬로 실행** +- `CompletionService`를 통해 **완료된 이미지부터 순서 없이 즉시 렌더링** + +→ 전체 실행 시간 감소, 응답 속도 향상 + +```java +public class Renderer { + private final ExecutorService executor; + + Renderer(ExecutorService executor) { + this.executor = executor; + } + + void renderPage(CharSequence source) { + final List info = scanForImageInfo(source); + CompletionService completionService = + new ExecutorCompletionService(executor); + for (final ImageInfo imageInfo : info) + completionService.submit(new Callable() { + public ImageData call() { + return imageInfo.downloadImage(); + } + }); + + renderText(source); + + try { + for (int t = 0, n = info.size(); t < n; t++) { + Future f = completionService.take(); + ImageData imageData = f.get(); + renderImage(imageData); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + throw launderThrowable(e.getCause()); + } + } +} +``` + +- 하나의 `Executor`에 대해 여러 개의 `ExecutorCompletionService` 생성 가능 +- `Future`는 단일 작업 상태 관리, `CompletionService`는 배치 작업 상태 관리에 적합 + +### **3.7 작업 실행 시간 제한** + +- `Future.get(timeout, unit)` 사용 → 지정 시간 초과 시 `TimeoutException` 발생 +- 작업이 제한 시간 내 완료되면 즉시 반환, 그렇지 않으면 실행 멈춤 + +→ 제한 시간이 지나면 작업을 스스로 멈추거나 `future.cancel(true)`로 강제 취소 가능 + +불필요한 자원 소모 방지 목적 + +- **광고가 포함된 웹 페이지 생성** + - 광고를 비동기로 가져오되, **제한 시간 초과 시 기본 광고 사용** + - 광고 작업은 `Future`로 실행하고, 타임아웃 발생 시 `cancel()` 호출 + +### **3.8 예제: 여행 예약 포털** + +- 여행 예약 포털은 항공사, 호텔 등 여러 업체의 입찰 정보를 병렬로 수집함. +- 업체별 입찰 요청은 독립적인 작업이므로 병렬 처리가 적합. +- 응답이 느린 업체는 제한 시간 이후 제외하거나 기본 메시지 제공. + +> **구현 방식** +> +- `QuoteTask`: `Callable` 구현 → 각 업체에 입찰 요청 +- `invokeAll(tasks, timeout, unit)`: + - 모든 작업을 스레드 풀에 등록 후, 제한 시간까지 대기 + - 리턴된 `Future` 목록은 원래 `tasks` 순서와 동일 +- 리턴 시점: + - 모든 작업이 완료되었거나 + - 제한 시간이 경과하거나 + - 호출한 스레드에 인터럽트 발생 + +```java +for (Future f : futures) { + try { + quotes.add(f.get()); + } catch (ExecutionException e) { + quotes.add(task.getFailureQuote(e.getCause())); + } catch (CancellationException e) { + quotes.add(task.getTimeoutQuote(e)); + } +} +``` + +- `get()`으로 결과 수집, 실패/타임아웃 시 예외 처리 +- `invokeAll()` 이후 작업 상태는 **완료 or 취소** 둘 중 하나 +- 정렬 후 사용자에게 응답 + +## 요약 + +- 애플리케이션을 작업 단위로 구조화하면 병렬성 확보 및 개발 간소화 가능 +- `Executor` 프레임워크를 사용하면, **작업 생성 로직과 실행 정책을 분리**하여 유연한 실행 제어 가능 +- 스레드를 직접 생성하는 대신 Executor 사용 권장 +- 병렬 처리가 유의미한 경우를 분석하여 **병렬화 가능한 작업 범위**를 적절히 정의할 필요 있음 \ No newline at end of file