이 코드는 여러 Agent(작업자)가 병렬로 작업을 수행하는 멀티스레딩 예제예요! 각 Agent마다 다른 작업량을 할당하고 동시에 실행합니다.
코드 분석:
1. AgentWorker 클래스 (Runnable 구현)
static class AgentWorker implements Runnable {
private final String agentName; // Agent 이름 (A, B, C)
private final int taskCount; // 각 Agent의 작업량
private final int input; // 초기 입력값
@Override
public void run() {
int result = 0;
for (int i = 0; i < taskCount; i++) {
result += (input + i); // input(100) + 0, 1, 2, ...
}
System.out.println(agentName + " 작업 완료: " + result);
}
}2. 작업 할당 정책
Map<String, Integer> policy = Map.of("A", 50, "B", 30, "C", 20);
// Agent명, 작업량- Agent A: 50번 작업
- Agent B: 30번 작업
- Agent C: 20번 작업
3. 스레드 생성 및 실행
List<Thread> threads = new ArrayList<>();
for (Map.Entry<String, Integer> entry : policy.entrySet()) {
Thread t = new Thread(new AgentWorker(entry.getKey(), entry.getValue(), input));
threads.add(t);
t.start(); // 즉시 시작
}4. 모든 스레드 대기 (join)
for (Thread t : threads) {
try {
t.join(); // 각 스레드가 끝날 때까지 메인 스레드 대기
} catch (InterruptedException e) {
System.out.println("스레드 대기 중 인터럽트 발생");
}
}실행 결과 계산:
Agent A (50번 작업):
result = (100+0) + (100+1) + (100+2) + ... + (100+49)
= 100×50 + (0+1+2+...+49)
= 5000 + 1225 = 6225Agent B (30번 작업):
result = 100×30 + (0+1+2+...+29) = 3000 + 435 = 3435Agent C (20번 작업):
result = 100×20 + (0+1+2+...+19) = 2000 + 190 = 2190실행 출력 (순서는 랜덤):
C 작업 완료: 2190
A 작업 완료: 6225
B 작업 완료: 3435
모든 Agent 작업 완료실제 응용 사례:
1. 파일 처리 분산:
static class FileProcessor implements Runnable {
private final String processorName;
private final List<String> files;
public FileProcessor(String name, List<String> files) {
this.processorName = name;
this.files = files;
}
@Override
public void run() {
for (String file : files) {
// 파일 처리 로직
processFile(file);
}
System.out.println(processorName + " 파일 처리 완료: " + files.size() + "개");
}
}2. 데이터베이스 배치 작업:
static class DBWorker implements Runnable {
private final String workerName;
private final int startId;
private final int endId;
public DBWorker(String name, int start, int end) {
this.workerName = name;
this.startId = start;
this.endId = end;
}
@Override
public void run() {
// ID 범위별로 데이터베이스 작업
for (int id = startId; id <= endId; id++) {
updateRecord(id);
}
System.out.println(workerName + " DB 작업 완료: " + (endId - startId + 1) + "건");
}
}3. 웹 크롤링 분산:
static class WebCrawler implements Runnable {
private final String crawlerName;
private final List<String> urls;
public WebCrawler(String name, List<String> urls) {
this.crawlerName = name;
this.urls = urls;
}
@Override
public void run() {
for (String url : urls) {
crawlWebsite(url);
}
System.out.println(crawlerName + " 크롤링 완료: " + urls.size() + "사이트");
}
}개선 버전 (ExecutorService 사용):
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ImprovedMultiAgentExample {
public static void main(String[] args) {
Map<String, Integer> policy = Map.of("A", 50, "B", 30, "C", 20);
int input = 100;
// 스레드 풀 생성
ExecutorService executor = Executors.newFixedThreadPool(3);
// 작업 제출
for (Map.Entry<String, Integer> entry : policy.entrySet()) {
executor.submit(new AgentWorker(entry.getKey(), entry.getValue(), input));
}
// 종료 처리
executor.shutdown();
try {
executor.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
executor.shutdownNow();
}
System.out.println("모든 Agent 작업 완료");
}
}핵심 개념:
- 병렬 처리: 여러 작업을 동시에 실행
- 작업 분할: 큰 작업을 작은 단위로 나누어 배분
- 스레드 조인: 모든 작업이 끝날 때까지 대기
- 리소스 관리: 각 Agent마다 다른 작업량 할당
이런 패턴은 대용량 데이터 처리, 배치 작업, 병렬 연산 등에서 성능 향상을 위해 자주 사용돼요! 🚀
이 코드는 Callable과 Future를 사용해서 작업 결과를 받아오는 멀티스레딩 예제예요! 이전 Runnable 예제와 달리 작업 결과를 반환받을 수 있어요.
주요 차이점:
Runnable vs Callable
// Runnable: 결과 반환 불가
public interface Runnable {
void run(); // void 반환
}
// Callable: 결과 반환 가능
public interface Callable<V> {
V call() throws Exception; // 제네릭 타입 반환 + 예외 던지기 가능
}
코드 분석:
1. SumTask 클래스 (Callable 구현)
static class SumTask implements Callable<Integer> {
private final int start; // 시작값 (100)
private final int count; // 작업 횟수 (50, 30, 20)
@Override
public Integer call() {
int sum = 0;
for (int i = 0; i < count; i++) {
sum += (start + i); // 100+0, 100+1, 100+2, ...
}
return sum; // 결과 반환!
}
}
2. Future로 작업 제출 및 결과 받기
// 작업 제출 (즉시 Future 객체 반환)
Future<Integer> resultA = executor.submit(new SumTask(100, 50));
Future<Integer> resultB = executor.submit(new SumTask(100, 30));
Future<Integer> resultC = executor.submit(new SumTask(100, 20));
// 결과 받기 (블로킹 호출 - 작업 완료까지 대기)
System.out.println("A Agent 결과: " + resultA.get()); // 6225
System.out.println("B Agent 결과: " + resultB.get()); // 3435
System.out.println("C Agent 결과: " + resultC.get()); // 2190
계산 결과:
Agent A (start=100, count=50):
sum = 100 + 101 + 102 + ... + 149
= 100×50 + (0+1+2+...+49)
= 5000 + 1225 = 6225
Agent B (start=100, count=30):
sum = 100×30 + (0+1+2+...+29) = 3000 + 435 = 3435
Agent C (start=100, count=20):
sum = 100×20 + (0+1+2+...+19) = 2000 + 190 = 2190
Future의 주요 메서드:
Future<Integer> future = executor.submit(new SumTask(100, 50));
// 결과 가져오기 (완료까지 대기)
Integer result = future.get();
// 타임아웃과 함께 결과 가져오기
Integer result = future.get(5, TimeUnit.SECONDS);
// 작업 완료 여부 확인
boolean isDone = future.isDone();
// 작업 취소
boolean cancelled = future.cancel(true);
// 취소 여부 확인
boolean isCancelled = future.isCancelled();
실용적인 활용 예시:
1. 병렬 계산 후 결과 합산:
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<Integer>> futures = new ArrayList<>();
// 여러 작업 제출
for (int i = 0; i < 4; i++) {
int start = i * 1000;
futures.add(executor.submit(new SumTask(start, 1000)));
}
// 모든 결과 합산
int totalSum = 0;
for (Future<Integer> future : futures) {
totalSum += future.get();
}
System.out.println("전체 합계: " + totalSum);
executor.shutdown();
}
2. 웹 API 병렬 호출:
static class ApiCall implements Callable<String> {
private final String url;
public ApiCall(String url) {
this.url = url;
}
@Override
public String call() throws Exception {
// HTTP 호출 시뮬레이션
Thread.sleep(1000); // 1초 대기
return "Response from " + url;
}
}
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(3);
// 여러 API 동시 호출
Future<String> api1 = executor.submit(new ApiCall("http://api1.com"));
Future<String> api2 = executor.submit(new ApiCall("http://api2.com"));
Future<String> api3 = executor.submit(new ApiCall("http://api3.com"));
// 모든 응답 수집
System.out.println(api1.get());
System.out.println(api2.get());
System.out.println(api3.get());
executor.shutdown();
}
3. 예외 처리:
static class RiskyTask implements Callable<String> {
@Override
public String call() throws Exception {
if (Math.random() < 0.5) {
throw new Exception("작업 실패!");
}
return "성공!";
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new RiskyTask());
try {
String result = future.get();
System.out.println("결과: " + result);
} catch (ExecutionException e) {
System.out.println("작업 중 예외 발생: " + e.getCause().getMessage());
} catch (InterruptedException e) {
System.out.println("인터럽트 발생");
}
executor.shutdown();
}
핵심 장점:
- 결과 반환:
Runnable과 달리 작업 결과를 받을 수 있음 - 예외 처리:
call()메서드에서 예외를 던질 수 있음 - 비동기 처리: 작업을 제출하고 나중에 결과만 받아옴
- 타임아웃: 일정 시간 후 포기 가능
- 취소 가능: 실행 중인 작업을 중단 가능
Callable/Future 패턴은 결과가 필요한 비동기 작업에 정말 유용해요! 🎯