add user agent parser and update build configuration

This commit is contained in:
2025-03-11 11:32:36 +09:00
parent 3db3499d30
commit 09731f464d
47 changed files with 8825 additions and 18 deletions

View File

@@ -0,0 +1,199 @@
자바의 `Thread` 클래스와 관련된 주요 메서드들을 표로 정리하고, 각 메서드에 대한 설명과 예시를 포함한 글을 작성하겠습니다. 아래는 요청하신 내용입니다.
---
### 자바 Thread 관련 메서드 표
| 메서드명 | 반환 타입 | 설명 |
|-------------------------|-----------|--------------------------------------------------------------------------------------|
| `start()` | `void` | 스레드를 시작하며, `run()` 메서드를 호출하여 실행 준비 상태로 만듦 |
| `run()` | `void` | 스레드가 실행할 작업을 정의 (직접 호출 시 스레드 생성 없이 현재 스레드에서 실행) |
| `sleep(long millis)` | `void` | 지정된 시간(밀리초) 동안 스레드를 일시 정지 (정적 메서드) |
| `join()` | `void` | 해당 스레드가 종료될 때까지 현재 스레드가 기다림 |
| `interrupt()` | `void` | 스레드에 인터럽트를 발생시켜 작업을 중단 요청 |
| `isAlive()` | `boolean` | 스레드가 실행 중인지 여부를 확인 |
| `setPriority(int n)` | `void` | 스레드의 우선순위를 설정 (1~10 사이 값) |
| `getPriority()` | `int` | 스레드의 현재 우선순위를 반환 |
| `yield()` | `void` | 현재 스레드가 다른 스레드에게 실행 기회를 양보 (정적 메서드) |
| `currentThread()` | `Thread` | 현재 실행 중인 스레드 객체를 반환 (정적 메서드) |
---
### Thread 메서드 설명 및 예시
자바에서 스레드는 멀티태스킹을 구현하는 데 필수적인 요소입니다. `Thread` 클래스와 `Runnable` 인터페이스를 통해 스레드를 생성하고 관리할 수 있으며, 위 표에 정리된 메서드들은 스레드의 생명주기와 동작을 제어하는 데 사용됩니다. 아래에서는 주요 메서드들을 예시와 함께 설명하겠습니다.
#### 1. `start()`와 `run()`
- **`start()`**: 새로운 스레드를 생성하고 실행 가능한 상태로 만들어 `run()` 메서드를 호출합니다.
- **`run()`**: 실제 스레드가 수행할 작업을 정의합니다. 단, `run()`을 직접 호출하면 새로운 스레드가 생성되지 않고 현재 스레드에서 실행됩니다.
```java
class MyThread extends Thread {
public void run() {
System.out.println("스레드 실행 중: " + Thread.currentThread().getName());
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 새로운 스레드 생성 및 실행
// thread.run(); // 이렇게 호출하면 메인 스레드에서 실행됨
}
}
```
- 출력: `스레드 실행 중: Thread-0`
#### 2. `sleep(long millis)`
- 지정된 시간 동안 현재 스레드를 일시 정지합니다. 다른 스레드에게 CPU를 양보하며, `InterruptedException`을 처리해야 합니다.
```java
public class SleepExample {
public static void main(String[] args) {
try {
System.out.println("2초 후에 깨어납니다.");
Thread.sleep(2000); // 2초 대기
System.out.println("깨어났습니다!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
```
- 출력:
```
2초 후에 깨어납니다.
(2초 대기 후)
깨어났습니다!
```
#### 3. `join()`
- 다른 스레드가 종료될 때까지 현재 스레드가 기다리도록 합니다. 작업 순서를 보장할 때 유용합니다.
```java
class WorkerThread extends Thread {
public void run() {
System.out.println("작업 시작");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("작업 완료");
}
}
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
WorkerThread worker = new WorkerThread();
worker.start();
worker.join(); // worker 스레드가 끝날 때까지 대기
System.out.println("메인 스레드 계속 진행");
}
}
```
- 출력:
```
작업 시작
(1초 후)
작업 완료
메인 스레드 계속 진행
```
#### 4. `interrupt()`
- 스레드에 인터럽트를 발생시켜 작업을 중단하도록 요청합니다. `sleep()`이나 `wait()` 중인 스레드는 `InterruptedException`을 발생시킵니다.
```java
class InterruptThread extends Thread {
public void run() {
try {
while (true) {
System.out.println("작업 중...");
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("인터럽트 발생! 종료합니다.");
}
}
}
public class InterruptExample {
public static void main(String[] args) throws InterruptedException {
InterruptThread thread = new InterruptThread();
thread.start();
Thread.sleep(2000); // 2초 후 인터럽트
thread.interrupt();
}
}
```
- 출력:
```
작업 중...
작업 중...
작업 중...
인터럽트 발생! 종료합니다.
```
#### 5. `setPriority(int n)`와 `getPriority()`
- 스레드의 우선순위를 설정하고 확인합니다. 우선순위는 1(최저)에서 10(최고)까지이며, 기본값은 5입니다.
```java
public class PriorityExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> System.out.println("Thread 1 실행"));
Thread thread2 = new Thread(() -> System.out.println("Thread 2 실행"));
thread1.setPriority(Thread.MIN_PRIORITY); // 1
thread2.setPriority(Thread.MAX_PRIORITY); // 10
System.out.println("Thread 1 우선순위: " + thread1.getPriority());
System.out.println("Thread 2 우선순위: " + thread2.getPriority());
thread1.start();
thread2.start();
}
}
```
- 출력 (실행 순서는 보장되지 않음):
```
Thread 1 우선순위: 1
Thread 2 우선순위: 10
Thread 2 실행
Thread 1 실행
```
#### 6. `yield()`
- 현재 스레드가 다른 스레드에게 실행 기회를 양보합니다. CPU 스케줄링에 따라 결과가 달라질 수 있습니다.
```java
class YieldThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + " 실행 중");
Thread.yield(); // 다른 스레드에게 양보
}
}
}
public class YieldExample {
public static void main(String[] args) {
Thread thread1 = new YieldThread();
Thread thread2 = new YieldThread();
thread1.start();
thread2.start();
}
}
```
- 출력 (순서 무작위):
```
Thread-0 실행 중
Thread-1 실행 중
Thread-0 실행 중
Thread-1 실행 중
...
```
---
### 결론
자바의 `Thread` 클래스는 스레드 생성, 실행, 제어를 위한 다양한 메서드를 제공합니다. `start()`와 `run()`으로 스레드를 시작하고, `sleep()`, `join()`, `interrupt()`로 동작을 제어하며, `setPriority()`와 `yield()`로 스케줄링을 조정할 수 있습니다. 위 예시를 통해 각 메서드의 동작을 이해하고, 실제 멀티스레드 프로그래밍에 적용해 보세요!

View File

@@ -0,0 +1,193 @@
### 자바 스레드 동기화에 대한 설명
스레드 동기화는 멀티스레드 환경에서 여러 스레드가 공유 자원(예: 변수, 객체 등)에 동시에 접근할 때 발생할 수 있는 데이터 불일치 문제를 해결하기 위해 사용됩니다. 자바에서는 동기화를 통해 특정 코드 블록이나 메서드가 한 번에 하나의 스레드만 실행하도록 보장합니다. 이를 통해 **경쟁 조건(race condition)**을 방지하고, 데이터의 무결성을 유지할 수 있습니다.
자바에서 스레드 동기화를 구현하는 주요 방법은 `synchronized` 키워드와 `java.util.concurrent` 패키지의 도구(예: `Lock`, `Semaphore` 등)를 사용하는 것입니다. 아래에서는 `synchronized` 키워드를 중심으로 설명하고, 예시를 제공하겠습니다.
---
### 스레드 동기화의 필요성
예를 들어, 은행 계좌 잔액을 관리하는 프로그램에서 두 스레드가 동시에 잔액을 수정하려고 하면 문제가 발생할 수 있습니다. 동기화가 없으면 잔액이 잘못 계산될 가능성이 높습니다. 이를 **경쟁 조건**이라고 부르며, 동기화로 해결할 수 있습니다.
---
### `synchronized` 키워드 사용 방법
자바에서 동기화는 두 가지 방식으로 적용됩니다:
1. **`synchronized` 메서드**: 메서드 전체를 동기화하여 한 번에 하나의 스레드만 실행하도록 만듭니다.
2. **`synchronized` 블록**: 특정 코드 블록만 동기화하여 더 세밀한 제어가 가능합니다.
---
### 예시 1: 동기화 없는 경우 (문제 발생)
아래는 동기화 없이 두 스레드가 계좌 잔액을 동시에 수정하는 예시입니다.
```java
class BankAccount {
private int balance = 1000; // 초기 잔액
public void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 출금 시도: " + amount);
balance -= amount; // 잔액 감소
System.out.println(Thread.currentThread().getName() + " 출금 후 잔액: " + balance);
}
}
public int getBalance() {
return balance;
}
}
public class NoSyncExample {
public static void main(String[] args) {
BankAccount account = new BankAccount();
Thread t1 = new Thread(() -> {
account.withdraw(800);
}, "스레드1");
Thread t2 = new Thread(() -> {
account.withdraw(800);
}, "스레드2");
t1.start();
t2.start();
}
}
```
- **출력 예시 (무작위 결과)**:
```
스레드1 출금 시도: 800
스레드2 출금 시도: 800
스레드1 출금 후 잔액: 200
스레드2 출금 후 잔액: -600
```
- **문제**: 두 스레드가 동시에 `withdraw`를 호출하면서 잔액이 음수가 되는 비정상적인 결과 발생. 이는 `if (balance >= amount)` 조건을 확인한 후 `balance -= amount`가 실행되기 전에 다른 스레드가 끼어들었기 때문입니다.
---
### 예시 2: `synchronized` 메서드로 동기화
`withdraw` 메서드에 `synchronized`를 적용하면 한 번에 하나의 스레드만 메서드를 실행할 수 있습니다.
```java
class BankAccount {
private int balance = 1000;
public synchronized void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 출금 시도: " + amount);
try {
Thread.sleep(100); // 경쟁 조건 시뮬레이션
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 출금 후 잔액: " + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 잔액 부족");
}
}
public int getBalance() {
return balance;
}
}
public class SyncMethodExample {
public static void main(String[] args) {
BankAccount account = new BankAccount();
Thread t1 = new Thread(() -> {
account.withdraw(800);
}, "스레드1");
Thread t2 = new Thread(() -> {
account.withdraw(800);
}, "스레드2");
t1.start();
t2.start();
}
}
```
- **출력 예시**:
```
스레드1 출금 시도: 800
스레드1 출금 후 잔액: 200
스레드2 출금 시도: 800
스레드2 잔액 부족
```
- **설명**: `synchronized` 덕분에 첫 번째 스레드가 `withdraw`를 완료할 때까지 두 번째 스레드가 대기하며, 잔액이 음수로 떨어지지 않음.
---
### 예시 3: `synchronized` 블록으로 동기화
메서드 전체가 아닌 특정 코드 블록만 동기화하려면 `synchronized` 블록을 사용합니다. 이때 동기화 대상 객체(락 객체)를 지정해야 합니다.
```java
class BankAccount {
private int balance = 1000;
private final Object lock = new Object(); // 락 객체
public void withdraw(int amount) {
System.out.println(Thread.currentThread().getName() + " 출금 시도: " + amount);
synchronized (lock) { // 동기화 블록
if (balance >= amount) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 출금 후 잔액: " + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 잔액 부족");
}
}
}
public int getBalance() {
return balance;
}
}
public class SyncBlockExample {
public static void main(String[] args) {
BankAccount account = new BankAccount();
Thread t1 = new Thread(() -> {
account.withdraw(800);
}, "스레드1");
Thread t2 = new Thread(() -> {
account.withdraw(800);
}, "스레드2");
t1.start();
t2.start();
}
}
```
- **출력 예시**:
```
스레드1 출금 시도: 800
스레드2 출금 시도: 800
스레드1 출금 후 잔액: 200
스레드2 잔액 부족
```
- **설명**: `synchronized (lock)` 블록 내의 코드만 동기화되며, `lock` 객체를 통해 스레드 간 접근을 제어함. 메서드 전체를 동기화하는 것보다 유연성이 높음.
---
### 동기화의 장점과 주의점
- **장점**:
- 공유 자원에 대한 안전한 접근 보장.
- 데이터 무결성 유지.
- **주의점**:
- 과도한 동기화는 성능 저하(데드락, 스레드 대기 시간 증가)를 유발할 수 있음.
- 필요한 범위만 동기화하도록 설계해야 함.
---
### 결론
스레드 동기화는 멀티스레드 프로그래밍에서 필수적인 개념으로, 자바에서는 `synchronized` 키워드를 통해 간단히 구현할 수 있습니다. `synchronized` 메서드와 블록을 적절히 사용하면 경쟁 조건을 방지하고 안정적인 프로그램을 작성할 수 있습니다. 위 예시를 참고하여 실제 애플리케이션에서 동기화를 적용해 보세요!

View File

@@ -0,0 +1,198 @@
### 자바의 `Timer`와 `TimerTask`에 대한 설명
자바에서 `Timer``TimerTask`는 특정 작업을 일정 시간 간격으로 또는 지정된 시간에 실행하기 위한 유틸리티 클래스입니다. `java.util` 패키지에 포함되어 있으며, 스레드를 기반으로 동작하여 비동기적으로 작업을 스케줄링할 수 있습니다. 아래에서는 이와 관련된 클래스 및 메서드를 표로 정리하고, 예시와 함께 설명하겠습니다.
---
### `Timer`와 `TimerTask` 관련 클래스 및 메서드 표
#### `Timer` 클래스
| 메서드명 | 반환 타입 | 설명 |
|-----------------------------------|-----------|---------------------------------------------------------------------------------------|
| `Timer()` | - | 새로운 `Timer` 객체를 생성 |
| `schedule(TimerTask, long delay)`| `void` | 지정된 지연 시간(밀리초) 후에 `TimerTask`를 한 번 실행 |
| `schedule(TimerTask, Date time)` | `void` | 지정된 시간(`Date`)에 `TimerTask`를 한 번 실행 |
| `schedule(TimerTask, long delay, long period)` | `void` | 지정된 지연 시간 후 주기적으로(밀리초 단위) `TimerTask`를 반복 실행 |
| `scheduleAtFixedRate(TimerTask, long delay, long period)` | `void` | 지정된 지연 시간 후 고정된 주기로 `TimerTask`를 반복 실행 (지연 보정 없음) |
| `cancel()` | `void` | `Timer`를 종료하고 모든 스케줄링된 작업을 취소 |
| `purge()` | `int` | 취소된 작업을 큐에서 제거하고 제거된 작업 수를 반환 |
#### `TimerTask` 클래스
| 메서드명 | 반환 타입 | 설명 |
|-----------------------------------|-----------|---------------------------------------------------------------------------------------|
| `run()` | `void` | `Timer`에 의해 실행될 작업을 정의 (추상 메서드, 오버라이드 필요) |
| `cancel()` | `boolean` | 해당 `TimerTask`의 스케줄링을 취소하고 성공 여부를 반환 |
| `scheduledExecutionTime()` | `long` | 해당 작업이 마지막으로 스케줄링된 시간을 반환 (밀리초 단위) |
---
### `Timer`와 `TimerTask` 설명 및 예시
`Timer`는 백그라운드 스레드를 생성하여 작업을 스케줄링하고, `TimerTask`는 실행할 작업을 정의하는 추상 클래스입니다. `TimerTask`를 상속받아 `run()` 메서드를 구현한 후, `Timer``schedule` 메서드로 작업을 등록합니다. 주요 사용 사례는 주기적인 알림, 로그 기록, 상태 점검 등입니다.
#### 1. 기본 사용: 일정 시간 후 한 번 실행
`schedule(TimerTask, long delay)`를 사용해 지정된 지연 시간 후 작업을 한 번 실행합니다.
```java
import java.util.Timer;
import java.util.TimerTask;
public class SimpleTimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("작업 실행: " + System.currentTimeMillis());
}
};
timer.schedule(task, 2000); // 2초 후 실행
System.out.println("스케줄링 완료: " + System.currentTimeMillis());
}
}
```
- **출력 예시**:
```
스케줄링 완료: 1678321234567
(2초 후)
작업 실행: 1678321236567
```
#### 2. 주기적 실행: 고정 주기 반복
`schedule(TimerTask, long delay, long period)`를 사용해 주기적으로 작업을 반복합니다.
```java
import java.util.Timer;
import java.util.TimerTask;
public class PeriodicTimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
int count = 0;
@Override
public void run() {
count++;
System.out.println("작업 실행 " + count + "회: " + System.currentTimeMillis());
if (count == 3) {
timer.cancel(); // 3회 실행 후 종료
System.out.println("타이머 종료");
}
}
};
timer.schedule(task, 1000, 2000); // 1초 후 시작, 2초마다 반복
}
}
```
- **출력 예시**:
```
(1초 후)
작업 실행 1회: 1678321235567
(2초 후)
작업 실행 2회: 1678321237567
(2초 후)
작업 실행 3회: 1678321239567
타이머 종료
```
#### 3. 고정 속도 실행: `scheduleAtFixedRate`
`scheduleAtFixedRate`는 작업 간 간격을 고정하며, 지연이 발생하더라도 이를 보정하지 않고 계속 실행합니다.
```java
import java.util.Timer;
import java.util.TimerTask;
public class FixedRateTimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
int count = 0;
@Override
public void run() {
count++;
System.out.println("작업 실행 " + count + "회: " + System.currentTimeMillis());
try {
Thread.sleep(1500); // 작업 시간이 주기보다 길어짐
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 3) {
timer.cancel();
}
}
};
timer.scheduleAtFixedRate(task, 1000, 1000); // 1초 후 시작, 1초마다 실행 시도
}
}
```
- **출력 예시**:
```
(1초 후)
작업 실행 1회: 1678321235567
(1.5초 후, 지연 발생)
작업 실행 2회: 1678321237067
(1.5초 후)
작업 실행 3회: 1678321238567
```
- **설명**: `scheduleAtFixedRate`는 주기를 엄격히 유지하려 하지만, 작업 시간이 주기(1초)보다 길어지면 다음 실행이 바로 이어집니다.
#### 4. 작업 및 타이머 취소
`TimerTask.cancel()`로 개별 작업을, `Timer.cancel()`로 모든 작업을 취소할 수 있습니다.
```java
import java.util.Timer;
import java.util.TimerTask;
public class CancelTimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("작업 실행");
}
};
timer.schedule(task, 1000, 1000); // 1초 후 시작, 1초마다 반복
try {
Thread.sleep(2500); // 2.5초 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
task.cancel(); // 개별 작업 취소
System.out.println("작업 취소됨");
timer.cancel(); // 타이머 종료
System.out.println("타이머 종료");
}
}
```
- **출력 예시**:
```
(1초 후)
작업 실행
(1초 후)
작업 실행
작업 취소됨
타이머 종료
```
---
### `Timer`와 `TimerTask`의 장점과 한계
- **장점**:
- 간단한 스케줄링 작업에 적합.
- 구현이 쉬움.
- **한계**:
- 단일 스레드로 동작하므로 하나의 작업이 지연되면 다른 작업에도 영향을 미침.
- 예외 발생 시 `Timer`가 종료될 수 있음.
- 복잡한 스케줄링에는 `ScheduledExecutorService`가 더 적합.
---
### 결론
`Timer`와 `TimerTask`는 주기적이거나 지연된 작업을 간단히 처리할 때 유용합니다. `schedule`과 `scheduleAtFixedRate`를 통해 다양한 스케줄링 요구를 충족할 수 있으며, `cancel`로 작업을 제어할 수 있습니다. 위 예시를 참고하여 실제 애플리케이션에서 활용해 보세요! 더 복잡한 경우에는 `java.util.concurrent.ScheduledExecutorService`를 고려하는 것도 좋은 대안입니다.

View File

@@ -0,0 +1,226 @@
### `CompletableFuture`에 대한 설명
`CompletableFuture`는 자바의 `java.util.concurrent` 패키지에 포함된 클래스입니다. 비동기 작업을 처리하고 결과를 조합하며, 예외 처리를 유연하게 다룰 수 있는 강력한 도구입니다. `Future` 인터페이스를 확장하여 논블록킹(non-blocking) 방식으로 작업을 연결하고, 작업 완료 시 후속 작업을 정의할 수 있습니다. 아래에서 주요 메서드를 표로 정리하고, 예시를 통해 사용 방법을 설명하겠습니다.
---
### `CompletableFuture` 주요 메서드 표
| 메서드명 | 반환 타입 | 설명 |
|---------------------------------------|----------------------------|----------------------------------------------------------------------------------------|
| `runAsync(Runnable)` | `CompletableFuture<Void>` | 지정된 `Runnable`을 비동기적으로 실행 |
| `supplyAsync(Supplier<T>)` | `CompletableFuture<T>` | 값을 제공하는 `Supplier`를 비동기적으로 실행하고 결과 반환 |
| `thenApply(Function<T, U>)` | `CompletableFuture<U>` | 이전 작업의 결과에 함수를 적용하여 변환된 결과 반환 |
| `thenAccept(Consumer<T>)` | `CompletableFuture<Void>` | 이전 작업의 결과를 소비(사용)하는 동작 수행 |
| `thenRun(Runnable)` | `CompletableFuture<Void>` | 이전 작업 완료 후 결과와 관계없이 실행할 작업 정의 |
| `thenCompose(Function<T, CompletableFuture<U>>)` | `CompletableFuture<U>` | 이전 결과에 따라 새로운 `CompletableFuture`를 반환 |
| `exceptionally(Function<Throwable, T>)` | `CompletableFuture<T>` | 예외 발생 시 복구 로직을 정의 |
| `handle(BiFunction<T, Throwable, U>)` | `CompletableFuture<U>` | 결과와 예외를 모두 처리하는 로직 정의 |
| `complete(T)` | `boolean` | 작업을 수동으로 완료하고 결과 설정 |
| `completeExceptionally(Throwable)` | `boolean` | 작업을 예외와 함께 수동으로 완료 |
| `join()` | `T` | 작업 완료를 기다리고 결과 반환 (예외 발생 시 던짐) |
| `get()` | `T` | `Future`처럼 결과를 기다리고 반환 (예외 처리 필요) |
| `allOf(CompletableFuture<?>...)` | `CompletableFuture<Void>` | 여러 `CompletableFuture`가 모두 완료될 때까지 대기 |
| `anyOf(CompletableFuture<?>...)` | `CompletableFuture<Object>`| 여러 `CompletableFuture` 중 하나가 완료되면 결과 반환 |
| `whenComplete(BiConsumer<T, Throwable>)` | `CompletableFuture<T>` | 작업 완료 시 결과와 예외를 처리 |
---
### `CompletableFuture` 설명 및 예시
`CompletableFuture`는 비동기 프로그래밍을 단순화하며, 작업 간 의존성을 표현하고 예외를 처리하는 데 유용합니다. 기본적으로 `ForkJoinPool.commonPool()`을 사용하지만, 사용자 지정 `Executor`를 전달할 수도 있습니다.
#### 1. 기본 비동기 작업: `supplyAsync`
비동기적으로 값을 생성합니다.
```java
import java.util.concurrent.*;
public class SupplyAsyncExample {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "비동기 작업 결과";
});
System.out.println("작업 시작");
String result = future.get();
System.out.println(result);
}
}
```
- **출력 예시**:
```
작업 시작
(1초 후)
비동기 작업 결과
```
#### 2. 결과 변환: `thenApply`
이전 작업의 결과를 변환합니다.
```java
import java.util.concurrent.*;
public class ThenApplyExample {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenApply(String::toUpperCase);
String result = future.get();
System.out.println(result);
}
}
```
- **출력 예시**:
```
HELLO WORLD
```
- **설명**: `thenApply`로 결과에 연속적인 변환을 적용.
#### 3. 결과 소비: `thenAccept`
결과를 소비하고 후속 작업을 정의합니다.
```java
import java.util.concurrent.*;
public class ThenAcceptExample {
public static void main(String[] args) throws Exception {
CompletableFuture.supplyAsync(() -> "데이터 처리")
.thenAccept(result -> System.out.println("결과: " + result))
.thenRun(() -> System.out.println("작업 완료"));
Thread.sleep(1000); // 비동기 작업 완료 대기
}
}
```
- **출력 예시**:
```
결과: 데이터 처리
작업 완료
```
#### 4. 예외 처리: `exceptionally`
예외 발생 시 복구 로직을 정의합니다.
```java
import java.util.concurrent.*;
public class ExceptionallyExample {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("에러 발생");
return "성공";
}).exceptionally(throwable -> "복구: " + throwable.getMessage());
String result = future.get();
System.out.println(result);
}
}
```
- **출력 예시**:
```
복구: java.lang.RuntimeException: 에러 발생
```
#### 5. 작업 조합: `thenCompose`
이전 결과에 따라 새로운 `CompletableFuture`를 연결합니다.
```java
import java.util.concurrent.*;
public class ThenComposeExample {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "첫 번째")
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " 두 번째"));
String result = future.get();
System.out.println(result);
}
}
```
- **출력 예시**:
```
첫 번째 두 번째
```
#### 6. 여러 작업 조합: `allOf`
여러 작업의 완료를 기다립니다.
```java
import java.util.concurrent.*;
public class AllOfExample {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(1000); } catch (Exception e) {}
return "작업 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "작업 2");
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
all.thenRun(() -> {
try {
System.out.println(future1.get() + " & " + future2.get());
} catch (Exception e) {
e.printStackTrace();
}
}).join();
}
}
```
- **출력 예시**:
```
작업 1 & 작업 2
```
#### 7. 수동 완료: `complete`
작업을 외부에서 수동으로 완료합니다.
```java
import java.util.concurrent.*;
public class CompleteExample {
public static void main(String[] args) {
CompletableFuture<String> future = new CompletableFuture<>();
new Thread(() -> {
try {
Thread.sleep(1000);
future.complete("수동 완료");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
String result = future.join();
System.out.println(result);
}
}
```
- **출력 예시**:
```
(1초 후)
수동 완료
```
---
### `CompletableFuture`의 장점과 주의점
- **장점**:
- 비동기 작업의 연속적 처리와 조합 가능.
- 예외 처리가 명시적이고 유연.
- 논블록킹 방식으로 작업 연결 가능.
- **주의점**:
- `get()`이나 `join()`을 과도히 사용하면 블록킹으로 인해 비동기 이점이 줄어듦.
- 복잡한 조합 시 코드 가독성 관리 필요.
---
### 결론
`CompletableFuture`는 비동기 작업을 정의하고, 결과를 변환하며, 예외를 처리하는 데 탁월합니다. `supplyAsync`, `thenApply`, `exceptionally`, `allOf` 등 다양한 메서드를 활용하면 작업 흐름을 효율적으로 설계할 수 있습니다. 위 예시를 참고하여 실제 애플리케이션에서 비동기 프로그래밍을 구현해 보세요!

View File

@@ -0,0 +1,401 @@
### 자바의 `Executor`와 관련된 설명
자바의 `Executor` 프레임워크는 스레드 풀을 관리하고 작업을 비동기적으로 실행하기 위한 강력한 도구로, `java.util.concurrent` 패키지에 포함되어 있습니다. `Executor` 인터페이스를 기반으로, 스레드 생성과 관리를 직접 다루는 대신 작업 단위로 실행을 위임하여 코드의 가독성과 효율성을 높입니다. 주요 클래스와 메서드를 표로 정리하고, 예시를 통해 사용 방법을 설명하겠습니다.
---
### `Executor`와 관련된 클래스 및 메서드 표
#### `Executor` 인터페이스
| 메서드명 | 반환 타입 | 설명 |
|-------------------------|-----------|---------------------------------------------------|
| `execute(Runnable)` | `void` | `Runnable` 작업을 실행하도록 스레드 풀에 위임 |
#### `ExecutorService` 인터페이스 (확장 인터페이스)
| 메서드명 | 반환 타입 | 설명 |
|-----------------------------------|-------------------|----------------------------------------------------------------------|
| `submit(Runnable)` | `Future<?>` | `Runnable` 작업을 실행하고 결과를 추적할 수 있는 `Future` 반환 |
| `submit(Callable<T>)` | `Future<T>` | `Callable` 작업을 실행하고 결과를 반환하는 `Future` 반환 |
| `shutdown()` | `void` | 새로운 작업을 받지 않고, 기존 작업 완료 후 종료 |
| `shutdownNow()` | `List<Runnable>` | 즉시 종료 시도하고 실행 대기 중인 작업 목록 반환 |
| `awaitTermination(long, TimeUnit)` | `boolean` | 지정된 시간 동안 종료를 기다리며, 완료 여부 반환 |
| `invokeAll(Collection<Callable<T>>)` | `List<Future<T>>` | 모든 `Callable` 작업을 실행하고 결과 목록 반환 |
| `invokeAny(Collection<Callable<T>>)` | `T` | 주어진 `Callable` 중 하나가 완료되면 그 결과를 반환 |
#### `Executors` 유틸리티 클래스 (정적 팩토리 메서드)
| 메서드명 | 반환 타입 | 설명 |
|-----------------------------------|-------------------|----------------------------------------------------------------------|
| `newFixedThreadPool(int n)` | `ExecutorService` | 고정 크기의 스레드 풀 생성 |
| `newSingleThreadExecutor()` | `ExecutorService` | 단일 스레드로 작업을 순차적으로 실행하는 풀 생성 |
| `newCachedThreadPool()` | `ExecutorService` | 필요에 따라 스레드를 생성/재사용하는 동적 풀 생성 |
| `newScheduledThreadPool(int n)` | `ScheduledExecutorService` | 주기적 작업을 스케줄링할 수 있는 스레드 풀 생성 |
#### `ScheduledExecutorService` 인터페이스 (확장 인터페이스)
| 메서드명 | 반환 타입 | 설명 |
|-----------------------------------|-------------------|----------------------------------------------------------------------|
| `schedule(Runnable, long, TimeUnit)` | `ScheduledFuture<?>` | 지정된 지연 시간 후 작업 실행 |
| `scheduleAtFixedRate(Runnable, long, long, TimeUnit)` | `ScheduledFuture<?>` | 고정 주기로 작업 반복 실행 |
| `scheduleWithFixedDelay(Runnable, long, long, TimeUnit)` | `ScheduledFuture<?>` | 작업 완료 후 고정 지연을 두고 반복 실행 |
#### 관련 클래스
| 클래스명 | 설명 |
|-------------------------|----------------------------------------------------------------------|
| `ThreadPoolExecutor` | 스레드 풀의 세부 설정(코어 풀 크기, 최대 풀 크기 등)을 커스터마이징 가능 |
| `Future<T>` | 비동기 작업의 결과를 추적하거나 취소할 수 있는 인터페이스 |
| `Callable<T>` | `Runnable`과 유사하나 결과를 반환하며 예외를 던질 수 있는 인터페이스 |
---
### `Executor` 설명 및 예시
`Executor` 프레임워크는 스레드 관리의 복잡성을 줄이고, 작업 실행을 추상화하여 재사용 가능한 스레드 풀을 제공합니다. 아래에서 주요 사용 사례를 예시로 설명합니다.
#### 1. 기본 사용: `ExecutorService`와 `newFixedThreadPool`
고정 크기의 스레드 풀을 생성하고 작업을 실행합니다.
```java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2); // 2개 스레드 풀
for (int i = 0; i < 5; i++) {
int taskId = i;
executor.execute(() -> {
System.out.println("작업 " + taskId + " 실행 중: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown(); // 작업 제출 후 종료 요청
}
}
```
- **출력 예시**:
```
작업 0 실행 중: pool-1-thread-1
작업 1 실행 중: pool-1-thread-2
(1초 후)
작업 2 실행 중: pool-1-thread-1
작업 3 실행 중: pool-1-thread-2
(1초 후)
작업 4 실행 중: pool-1-thread-1
```
- **설명**: 2개의 스레드가 최대 2개 작업을 동시에 처리하며, 나머지는 대기 후 실행됨.
#### 2. `Callable`과 `Future`로 결과 받기
`submit`을 사용해 결과를 반환하는 작업을 실행합니다.
```java
import java.util.concurrent.*;
public class CallableExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 42;
};
Future<Integer> future = executor.submit(task);
System.out.println("작업 제출 완료");
Integer result = future.get(); // 작업 완료까지 대기 후 결과 가져오기
System.out.println("결과: " + result);
executor.shutdown();
}
}
```
- **출력 예시**:
```
작업 제출 완료
(1초 후)
결과: 42
```
- **설명**: `Future.get()`은 작업이 완료될 때까지 블록하며, 결과를 반환받음.
#### 3. 주기적 실행: `ScheduledExecutorService`
`scheduleAtFixedRate`로 주기적으로 작업을 실행합니다.
```java
import java.util.concurrent.*;
public class ScheduledExecutorExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("작업 실행: " + System.currentTimeMillis());
executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS); // 1초 후 시작, 2초 주기
try {
Thread.sleep(5000); // 5초 동안 실행
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
```
- **출력 예시**:
```
(1초 후)
작업 실행: 1678321234567
(2초 후)
작업 실행: 1678321236567
(2초 후)
작업 실행: 1678321238567
```
- **설명**: 작업이 고정된 2초 주기로 실행됨.
#### 4. `invokeAll`로 여러 작업 실행
여러 `Callable` 작업을 한 번에 실행하고 결과를 수집합니다.
```java
import java.util.concurrent.*;
import java.util.Arrays;
import java.util.List;
public class InvokeAllExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2);
List<Callable<String>> tasks = Arrays.asList(
() -> { Thread.sleep(1000); return "작업 1 완료"; },
() -> { Thread.sleep(500); return "작업 2 완료"; },
() -> { Thread.sleep(1500); return "작업 3 완료"; }
);
List<Future<String>> futures = executor.invokeAll(tasks);
for (Future<String> future : futures) {
System.out.println(future.get());
}
executor.shutdown();
}
}
```
- **출력 예시**:
```
작업 1 완료
작업 2 완료
작업 3 완료
```
- **설명**: 모든 작업이 완료될 때까지 대기한 후 결과를 순차적으로 출력.
---
### `Executor`의 장점과 주의점
- **장점**:
- 스레드 풀을 재사용하여 리소스 낭비 감소.
- 작업 실행과 스레드 관리를 분리하여 코드 간소화.
- 주기적 작업 및 결과 처리가 용이.
- **주의점**:
- `shutdown()`을 호출하지 않으면 프로그램이 종료되지 않을 수 있음.
- 스레드 풀 크기와 작업 부하를 적절히 조정해야 성능 최적화 가능.
---
### 결론
`Executor` 프레임워크는 스레드 관리의 복잡성을 줄이고, 다양한 실행 패턴(단일 실행, 주기적 실행, 병렬 실행 등)을 지원합니다. `ExecutorService`, `ScheduledExecutorService`, `Executors`를 활용하면 효율적인 멀티스레드 애플리케이션을 쉽게 구현할 수 있습니다. 위 예시를 참고하여 실제 프로젝트에 적용해 보세요!
---
### `ScheduledExecutorService`와 다른 스케줄링 도구 비교
자바에서 주기적이거나 지연된 작업을 스케줄링할 때 `ScheduledExecutorService`는 강력한 도구로 사용됩니다. 하지만 `java.util.Timer`와 `CompletableFuture`와 같은 대안도 있으며, 각각의 특징과 사용 사례가 다릅니다. 아래에서 `ScheduledExecutorService`를 `Timer`와 `CompletableFuture`와 비교하여 표로 정리하고, 예시와 함께 설명하겠습니다.
---
### 비교 표
| **특징** | **`ScheduledExecutorService`** | **`Timer`** | **`CompletableFuture`** |
|---------------------------|-----------------------------------------------------|------------------------------------------|------------------------------------------|
| **패키지** | `java.util.concurrent` | `java.util` | `java.util.concurrent` |
| **스레드 모델** | 스레드 풀 기반 (다중 스레드 지원) | 단일 스레드 기반 | 기본 풀(`ForkJoinPool`) 또는 사용자 지정 |
| **작업 타입** | `Runnable`, `Callable` | `TimerTask` | `Runnable`, `Supplier` |
| **스케줄링 메서드** | `schedule`, `scheduleAtFixedRate`, `scheduleWithFixedDelay` | `schedule`, `scheduleAtFixedRate` | `supplyAsync` + `then` 조합으로 간접 지원 |
| **고정 주기 지원** | `scheduleAtFixedRate` (고정 주기), `scheduleWithFixedDelay` (고정 지연) | `scheduleAtFixedRate` (고정 주기) | 직접 지원 없음, 별도 로직 필요 |
| **예외 처리** | 개별 작업 예외가 전체에 영향 없음 | 예외 발생 시 `Timer` 종료 가능 | `exceptionally` 등으로 명시적 처리 가능 |
| **취소 기능** | `ScheduledFuture.cancel()` | `TimerTask.cancel()`, `Timer.cancel()` | `complete()`, `cancel()` |
| **복잡한 작업 조합** | 제한적 | 없음 | `thenApply`, `allOf` 등으로 가능 |
| **성능 및 확장성** | 높은 부하와 복잡한 작업에 적합 | 간단한 작업에 적합 | 비동기 작업 조합에 강력 |
| **사용 용도** | 주기적 작업, 다중 스레드 스케줄링 | 간단한 단일 스레드 스케줄링 | 비동기 작업 흐름 관리 |
---
### 상세 설명 및 예시
#### 1. `ScheduledExecutorService`
- **특징**: 스레드 풀을 활용하여 다중 스레드로 작업을 처리하며, 예외 발생 시 다른 작업에 영향을 주지 않습니다.
- **예시**: 주기적으로 상태를 점검하는 작업.
```java
import java.util.concurrent.*;
public class ScheduledExecutorExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable task = () -> System.out.println("실행: " + System.currentTimeMillis());
executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
```
- **출력 예시**:
```
실행: 1678321234567
실행: 1678321236567
실행: 1678321238567
```
#### 2. `Timer`
- **특징**: 단일 스레드로 동작하며, 작업 중 예외가 발생하면 `Timer`가 종료될 수 있습니다.
- **예시**: 간단한 알림 기능.
```java
import java.util.*;
public class TimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("실행: " + System.currentTimeMillis());
}
};
timer.scheduleAtFixedRate(task, 1000, 2000);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
timer.cancel();
}
}
```
- **출력 예시**:
```
실행: 1678321234567
실행: 1678321236567
실행: 1678321238567
```
#### 3. `CompletableFuture`
- **특징**: 주기적 스케줄링은 직접 지원하지 않지만, 비동기 작업 조합과 예외 처리가 뛰어납니다.
- **예시**: 작업 완료 후 후속 작업 실행.
```java
import java.util.concurrent.*;
public class CompletableFutureExample {
public static void main(String[] args) throws Exception {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("실행: " + System.currentTimeMillis());
}).thenRun(() -> System.out.println("후속 작업"));
future.get();
}
}
```
- **출력 예시**:
```
실행: 1678321235567
후속 작업
```
#### 비교 예시: 예외 발생 시 동작
- **`ScheduledExecutorService`**와 **`Timer`**의 차이점을 보여줍니다.
```java
import java.util.*;
import java.util.concurrent.*;
public class ExceptionComparison {
public static void main(String[] args) {
// Timer 예시
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
int count = 0;
@Override
public void run() {
count++;
System.out.println("Timer 실행 " + count);
if (count == 2) throw new RuntimeException("Timer 예외");
}
}, 0, 1000);
// ScheduledExecutorService 예시
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(() -> {
int count = 0;
count++;
System.out.println("Executor 실행 " + count);
if (count == 2) throw new RuntimeException("Executor 예외");
}, 0, 1, TimeUnit.SECONDS);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
timer.cancel();
executor.shutdown();
}
}
```
- **출력 예상**:
```
Timer 실행 1
Timer 실행 2
(예외 발생 후 Timer 종료, 더 이상 실행 안 됨)
Executor 실행 1
Executor 실행 2
(예외 발생 후에도 다른 작업은 계속 실행 가능)
```
- **설명**: `Timer`는 예외로 종료되지만, `ScheduledExecutorService`는 영향을 받지 않음.
---
### 선택 가이드
- **`ScheduledExecutorService`**:
- 다중 스레드와 안정성이 필요한 주기적 작업.
- 복잡한 작업 관리와 확장성 요구 시.
- **`Timer`**:
- 간단한 단일 스레드 작업.
- 경량 애플리케이션에서 최소한의 스케줄링 필요 시.
- **`CompletableFuture`**:
- 비동기 작업 흐름을 조합하거나 단일 작업의 후속 처리가 필요할 때.
- 주기적 스케줄링보다는 결과 기반 작업에 적합.
---
### 결론
`ScheduledExecutorService`는 스레드 풀 기반으로 안정성과 유연성을 제공하며, `Timer`는 간단한 작업에 적합하지만 취약점이 있습니다. `CompletableFuture`는 주기적 스케줄링보다는 비동기 작업 조합에 강점을 가집니다. 요구사항에 따라 적절한 도구를 선택해 사용하세요!

View File

@@ -0,0 +1,210 @@
### 자바의 `Runnable`, `Callable`, `Future`, `CompletableFuture`에 대한 설명
자바에서 멀티스레드 프로그래밍을 지원하는 주요 인터페이스와 클래스는 `Runnable`, `Callable`, `Future`, 그리고 `CompletableFuture`입니다. 이들은 작업을 정의하고, 실행하며, 결과를 처리하는 데 사용됩니다. 아래에서 각 클래스와 관련 메서드를 표로 정리하고, 예시를 통해 사용 방법을 설명하겠습니다.
---
### 관련 클래스 및 메서드 표
#### `Runnable` 인터페이스
| 메서드명 | 반환 타입 | 설명 |
|-----------------|-----------|---------------------------------------------------|
| `run()` | `void` | 스레드가 실행할 작업을 정의 (추상 메서드) |
#### `Callable<V>` 인터페이스
| 메서드명 | 반환 타입 | 설명 |
|-----------------|-----------|---------------------------------------------------|
| `call()` | `V` | 결과를 반환하며 예외를 던질 수 있는 작업 정의 |
#### `Future<V>` 인터페이스
| 메서드명 | 반환 타입 | 설명 |
|-------------------------|-----------|---------------------------------------------------|
| `get()` | `V` | 작업 결과를 반환 (완료될 때까지 대기) |
| `get(long, TimeUnit)` | `V` | 지정된 시간 동안 대기 후 결과 반환 |
| `cancel(boolean)` | `boolean` | 작업을 취소 시도하고 성공 여부 반환 |
| `isDone()` | `boolean` | 작업이 완료되었는지 확인 |
| `isCancelled()` | `boolean` | 작업이 취소되었는지 확인 |
#### `CompletableFuture<V>` 클래스
| 메서드명 | 반환 타입 | 설명 |
|-----------------------------------|-------------------------|----------------------------------------------------------------------|
| `runAsync(Runnable)` | `CompletableFuture<Void>` | 비동기적으로 `Runnable` 실행 |
| `supplyAsync(Supplier<V>)` | `CompletableFuture<V>` | 비동기적으로 값을 제공하는 작업 실행 |
| `thenApply(Function<T, U>)` | `CompletableFuture<U>` | 이전 작업의 결과에 함수를 적용하여 변환된 결과 반환 |
| `thenAccept(Consumer<T>)` | `CompletableFuture<Void>` | 결과에 대한 소비 동작 수행 |
| `thenRun(Runnable)` | `CompletableFuture<Void>` | 결과와 상관없이 후속 작업 실행 |
| `exceptionally(Function<Throwable, T>)` | `CompletableFuture<T>` | 예외 발생 시 처리 로직 정의 |
| `complete(V)` | `boolean` | 작업을 수동으로 완료하고 결과 설정 |
| `join()` | `V` | 작업 완료를 기다리고 결과 반환 (스레드 풀에서 사용 시 주의) |
| `allOf(CompletableFuture<?>...)` | `CompletableFuture<Void>` | 여러 `CompletableFuture`가 모두 완료될 때까지 대기 |
| `anyOf(CompletableFuture<?>...)` | `CompletableFuture<Object>` | 여러 `CompletableFuture` 중 하나가 완료되면 결과 반환 |
---
### 설명 및 예시
이들 클래스는 멀티스레드 작업의 정의와 실행, 결과 처리를 다룹니다. `Runnable`은 기본적인 작업 단위이고, `Callable`은 결과를 반환하며, `Future`는 결과를 추적하고, `CompletableFuture`는 비동기 작업의 조합과 예외 처리를 지원합니다.
#### 1. `Runnable`: 기본 작업 정의
`Runnable`은 결과를 반환하지 않는 간단한 작업을 정의합니다.
```java
public class RunnableExample {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Runnable 작업 실행: " + Thread.currentThread().getName());
};
Thread thread = new Thread(task);
thread.start();
}
}
```
- **출력 예시**:
```
Runnable 작업 실행: Thread-0
```
#### 2. `Callable`과 `Future`: 결과 반환
`Callable`은 값을 반환하며, `Future`로 결과를 받습니다.
```java
import java.util.concurrent.*;
public class CallableFutureExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<String> task = () -> {
Thread.sleep(1000);
return "Callable 작업 완료";
};
Future<String> future = executor.submit(task);
System.out.println("작업 제출 완료");
String result = future.get(); // 완료 대기
System.out.println(result);
executor.shutdown();
}
}
```
- **출력 예시**:
```
작업 제출 완료
(1초 후)
Callable 작업 완료
```
- **설명**: `Future.get()`은 작업이 끝날 때까지 대기하며 결과를 반환.
#### 3. `CompletableFuture`: 비동기 작업 조합
`CompletableFuture`는 비동기 작업을 연결하고 결과를 처리하는 데 유용합니다.
```java
import java.util.concurrent.*;
public class CompletableFutureExample {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "비동기 결과";
}).thenApply(result -> result + " 처리됨")
.thenApply(result -> result.toUpperCase());
String result = future.get();
System.out.println(result);
}
}
```
- **출력 예시**:
```
(1초 후)
비동기 결과 처리됨
```
- **설명**: `supplyAsync`로 값을 생성하고, `thenApply`로 결과를 변환.
#### 4. `CompletableFuture` 예외 처리 및 조합
예외 처리와 여러 작업의 동시 실행을 다룹니다.
```java
import java.util.concurrent.*;
public class CompletableFutureAdvancedExample {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "작업 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("작업 2 실패");
}).exceptionally(throwable -> "복구: " + throwable.getMessage());
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
all.thenRun(() -> {
try {
System.out.println(future1.get() + " & " + future2.get());
} catch (Exception e) {
e.printStackTrace();
}
}).join(); // 모든 작업 완료 대기
}
}
```
- **출력 예시**:
```
작업 1 & 복구: java.lang.RuntimeException: 작업 2 실패
```
- **설명**: `exceptionally`로 예외를 처리하고, `allOf`로 여러 작업을 조합.
#### 5. `CompletableFuture`로 수동 완료
`complete`를 사용해 작업을 수동으로 완료할 수 있습니다.
```java
import java.util.concurrent.*;
public class ManualCompleteExample {
public static void main(String[] args) {
CompletableFuture<String> future = new CompletableFuture<>();
new Thread(() -> {
try {
Thread.sleep(1000);
future.complete("수동 완료");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
String result = future.join();
System.out.println(result);
}
}
```
- **출력 예시**:
```
(1초 후)
수동 완료
```
---
### 비교와 특징
- **`Runnable`**: 결과 없음, 단순 작업 실행.
- **`Callable`**: 결과 반환, `Future`와 함께 사용.
- **`Future`**: 비동기 작업 상태 추적, 블록킹 방식.
- **`CompletableFuture`**: 비동기 작업 조합, 예외 처리, 논블록킹 지원.
---
### 결론
`Runnable`과 `Callable`은 작업을 정의하고, `Future`는 결과를 추적하며, `CompletableFuture`는 복잡한 비동기 작업을 유연하게 처리합니다. 상황에 따라 적합한 클래스를 선택해 사용하면 효율적인 멀티스레드 프로그래밍이 가능합니다. 위 예시를 참고하여 실제 애플리케이션에 적용해 보세요!