JAVA7에서 추가된 Fork/Join Framework를 설명하기 전에 기존에 ThreadPool에 대해서 설명해보면 다음과 같다.
ThreadPool은 여러 스레드들 미리 생성하고 관리하는 프로그래밍 구조이다. 이를 통해서 멀티 스레드 애플리케이션에서의 성능을 향상 시킬수 있는데, 스레드를 필요할 때마다 생성하고, 소멸시키는 비용은 상당히 높기 때문에, 미리 생성된 스레드들을 재사용함으로써 이러한 비용을 절감할 수 있다는 장점이 있다.
JAVA에서는 'java.util.concurrent' 패키지 안에 'ExecutorService' 인터페이스와 'Excutors' 유틸리티 클래스를 통해 스레드 풀을 쉽게 사용할 수 있다.
Fork/Join Framwork는 JAVA 7에서 도입된 병렬 처리를 위한 프레임워크이고, 주로 큰 작업을 작은 작업으로 나누고 작업을 병렬로 처리한 후 그 결과를 합치는 분할 정복 방식에 최적화 되어 있다. 이 프레임워크는 'java.util.concurrent' 패키지의 일부로 특히 RecursiveAction과 RecursiveTask 두 가지 주요 추상 클래스를 제공한다.
작업 훔치기(Work-Stealing) 알고리즘을 사용하여, 각 스레드는 자신의 큐에 할당된 작업을 수행하다가 일을 마치면 다른 스레드의 큐에서 대기 중인 작업을 훔쳐와서 실행한다. 이 방식은 모든 스레드가 계속해서 작업을 처리할 수 있도록 하고 있기 때문에 리소스의 활용도를 높인다.
Fork/Join Framework는 복잡한 계산이나 대량의 데이터 처리에 적합하고, 일반적인 ThreadPool은 간단하고 반복적인 작업에 더 적합할 수 있다. ThreadPool과 Fork/Join Framework를 사용하는 예제 소스를 보면서 비교해보자.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(4); // 4개의 스레드를 가진 풀 생성
for (int i = 0; i < 20; i++) {
int taskNumber = i;
executor.execute(() -> {
System.out.println("Executing Task " + taskNumber + " on thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 시뮬레이션을 위한 1초 대기
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);
}
}
출력 결과
Executing Task 3 on thread pool-1-thread-4
Executing Task 0 on thread pool-1-thread-1
Executing Task 2 on thread pool-1-thread-3
Executing Task 1 on thread pool-1-thread-2
Executing Task 5 on thread pool-1-thread-1
Executing Task 4 on thread pool-1-thread-3
Executing Task 6 on thread pool-1-thread-2
Executing Task 7 on thread pool-1-thread-4
Executing Task 8 on thread pool-1-thread-1
Executing Task 9 on thread pool-1-thread-3
Executing Task 10 on thread pool-1-thread-2
Executing Task 11 on thread pool-1-thread-4
Executing Task 12 on thread pool-1-thread-1
Executing Task 13 on thread pool-1-thread-3
Executing Task 14 on thread pool-1-thread-2
Executing Task 15 on thread pool-1-thread-4
Executing Task 16 on thread pool-1-thread-1
Executing Task 17 on thread pool-1-thread-3
Executing Task 18 on thread pool-1-thread-2
Executing Task 19 on thread pool-1-thread-4
기존 JAVA에서 ExecutorService를 사용하여 간단한 작업을 병렬로 처리하는 예제이다.
이 코드는 20개의 작업을 생성하고, 고정된 크기(4개)의 스레드 풀에서 이 작업들을 실행한다. 각 작업은 현재 실행중인 스레드의 이름을 출력하고, 1초간 대기한다.
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
public class ForkJoinExample extends RecursiveAction {
private final int start;
private final int end;
private static final int THRESHOLD = 5;
public ForkJoinExample(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start <= THRESHOLD) {
for (int i = start; i < end; i++) {
System.out.println("Processing " + i + " on thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} else {
int mid = start + (end - start) / 2;
ForkJoinExample left = new ForkJoinExample(start, mid);
ForkJoinExample right = new ForkJoinExample(mid, end);
invokeAll(left, right);
}
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new ForkJoinExample(0, 20));
pool.shutdown();
}
}
출력 결과
Processing 15 on thread ForkJoinPool-1-worker-4
Processing 5 on thread ForkJoinPool-1-worker-3
Processing 10 on thread ForkJoinPool-1-worker-2
Processing 0 on thread ForkJoinPool-1-worker-1
Processing 16 on thread ForkJoinPool-1-worker-4
Processing 6 on thread ForkJoinPool-1-worker-3
Processing 11 on thread ForkJoinPool-1-worker-2
Processing 1 on thread ForkJoinPool-1-worker-1
Processing 17 on thread ForkJoinPool-1-worker-4
Processing 12 on thread ForkJoinPool-1-worker-2
Processing 7 on thread ForkJoinPool-1-worker-3
Processing 2 on thread ForkJoinPool-1-worker-1
Processing 13 on thread ForkJoinPool-1-worker-2
Processing 8 on thread ForkJoinPool-1-worker-3
Processing 3 on thread ForkJoinPool-1-worker-1
Processing 18 on thread ForkJoinPool-1-worker-4
Processing 14 on thread ForkJoinPool-1-worker-2
Processing 9 on thread ForkJoinPool-1-worker-3
Processing 19 on thread ForkJoinPool-1-worker-4
Processing 4 on thread ForkJoinPool-1-worker-1
이 코드는 비슷한 작업을 분할 정복 방식으로 처리하는 예제이다. 작업을 더 작은 단위로 나누고 각 부분을 별도의 스레드에서 처리한다. invokeAll 메소드는 분할된 작업들을 병렬로 실행하고 완료될 때까지 기다린다. 이 방식은 작업이 균등하게 분배되고, 스레드 풀의 스레드가 최대한 효율적으로 사용된다.