Fork/Join 프레임워크는 Java 7부터 도입된 병렬 처리를 위한 프레임워크다. 큰 작업을 작은 단위로 분할(fork)하고, 각각을 병렬로 처리한 후 결과를 합치는(join) 분할 정복(divide and conquer) 방식을 사용한다.
Work Stealing 알고리즘을 사용하는데, 각 스레드가 자신의 작업 큐를 가지고 있다가 작업이 없으면 다른 스레드의 큐에서 작업을 가져와 처리한다. 이를 통해 CPU 코어를 효율적으로 활용할 수 있다.
RecursiveTask<V>: 반환값이 있는 작업을 구현할 때 사용RecursiveAction: 반환값이 없는 작업을 구현할 때 사용두 클래스 모두 compute()라는 추상 메서드를 가지고 있고, 이 추상 메서드를 구현하면 된다.
compute 메서드는 태스크를 서브태스크로 분할하는 로직과 더 이상 분할할 수 없을 때 개별 서브태스크의 결과를 생산할 알고리즘을 정의한다. 따라서 대부분의 compute 메서드 구현은 다음과 같은 형식을 유지한다.
if (태스크가 충분히 작거나 더 이상 분할할 수 없으면) { 순차적으로 태스크 계산(알고리즘) } else { 태스크를 두 서브태스크로 분할(재귀적 호출, Fork) 모든 서브태스크의 연산이 완료될 때까지 기다림 각 서브태스크의 결과를 합칩(Join) }
class SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 1000;
private int[] array;
private int start, end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
int length = end - start;
// 작업이 충분히 작으면 직접 계산
if (length <= THRESHOLD) {
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
}
// 1. 작업을 둘로 나눔 (fork)
int mid = start + length / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
leftTask.fork(); // 2. 비동기 실행
long rightResult = rightTask.compute(); // 2-1. 현재 스레드에서 실행
long leftResult = leftTask.join(); // 3. 결과 대기
return leftResult + rightResult; // 결과 합침
}
}
// 사용
ForkJoinPool pool = new ForkJoinPool();
int[] array = new int[10000];
Long result = pool.invoke(new SumTask(array, 0, array.length));
위 코드는 배열의 합을 구하는 예시이다.
ForkJoinPool은 한번만 인스턴스화해서 정적 필드에 싱글톤으로 관리한다.Fork/Join 프레임워크는 Java 7부터 도입된 병렬 처리를 위한 프레임워크다. 큰 작업을 작은 단위로 분할(fork)하고, 각각을 병렬로 처리한 후 결과를 합치는(join) 분할 정복(divide and conquer) 방식을 사용한다. 주요 구성 요소는 ForkJoinPool과 ForkJoinTask가 있다. ForkJoinPool작업을 실행하는 스레드 풀이며 work-stealing 방식으로 동작한다. 각 스레드가 자신의 작업 큐를 가지고 있다가 작업이 없으면 다른 스레드의 큐에서 작업을 가져와 처리한다. 이를 통해 CPU 코어를 효율적으로 활용할 수 있다. ForkJoinTask는 실행할 작업을 나타내는 추상 클래스이다. 하위 클래스로는 RecursiveTask, RecursiveAction이 있다. 작업의 반환 값이 있는 경우에는 RecursiveTask를, 없는 경우에는 RecursiveAction을 상속받아서 구현하면된다. 이 클래스를 상속받으면 compute()를 오버라이딩 해야한다. compute 메서드는 태스크를 서브태스크로 분할하는 로직과 더 이상 분할할 수 없을 때 개별 서브태스크의 결과를 생산할 로직을 정의한다. Fork/Join 프레임워크를 사용하면 재귀적으로 분할 가능한 대용량 데이터 처리나 컬렉션의 병렬처리를 할 수 있다.