ForkJoinPool은 병렬 처리를 최적화하기 위해 Java 7에서 도입된 고급 멀티스레딩 도구로, 큰 작업을 작은 작업으로 분할(Fork)하고 여러 스레드에서 동시에 처리하여 그 결과를 합친(Join) 후 반환하는 방식으로 동작합니다. 주로 대규모 작업을 병렬로 처리할 때 성능을 최적화하는 데 사용됩니다.
1️⃣ 기본 개념
- Fork: 큰 작업을 작은 작업으로 나누어 병렬 처리.
- Join: 병렬로 처리된 작은 작업들을 다시 합쳐 최종 결과 생성.
ForkJoinPool은 분할-정복 알고리즘(divide and conquer)을 사용하여 대규모 작업을 효율적으로 처리합니다. 이를 통해 병렬성을 극대화하고, 여러 코어를 활용하여 성능을 향상시킬 수 있습니다.
2️⃣ ForkJoinPool 사용 이유
✅ 병렬 작업을 쉽게 처리
여러 개의 작은 작업들을 여러 스레드에서 동시에 처리할 수 있게 해줍니다. 일반적인 스레드 풀을 사용할 때보다 효율적으로 자원을 관리할 수 있습니다.
✅ 분할-정복 패턴
큰 작업을 여러 개의 작은 작업으로 나누어 동시에 처리하고, 그 결과를 합칩니다. 예: 병렬 계산, 대규모 데이터 처리 등.
3️⃣ ForkJoinPool 구조
ForkJoinPool은 기본적으로 두 가지 작업을 처리하는 방식으로 설계되었습니다:
- Work Stealing: 각 스레드는 자신의 작업 큐를 가집니다. 만약 스레드가 자신의 작업을 다 끝내면, 다른 스레드의 큐에서 작업을 빼와서 처리할 수 있습니다. 이 방법은 로드 밸런싱을 자연스럽게 제공합니다.
- RecursiveTask (결과를 반환하는 작업): 반환값이 있는 작업을 할 때 사용됩니다. 예: ForkJoinTask에서 fork()로 작업을 분할하고 join()으로 결과를 합칩니다.
- RecursiveAction (결과를 반환하지 않는 작업): 반환값이 없는 작업을 할 때 사용됩니다. 예: 데이터 변환, 파일 쓰기 등.
4️⃣ ForkJoinPool 클래스
ForkJoinPool은 java.util.concurrent 패키지의 클래스이며, 스레드를 동적으로 관리하고 작업을 분할하여 효율적으로 실행합니다. 기본적으로 fork와 join 작업을 관리하는 작업 큐를 사용합니다.
기본 사용 방법:
import java.util.concurrent.*;
public class ForkJoinExample {
public static void main(String[] args) {
// ForkJoinPool 생성
ForkJoinPool pool = new ForkJoinPool();
// 작업 제출
RecursiveTask task = new MyRecursiveTask(10);
Integer result = pool.invoke(task); // 작업 실행 및 결과 대기
// 결과 출력
System.out.println("Result: " + result);
}
}
class MyRecursiveTask extends RecursiveTask {
private int n;
public MyRecursiveTask(int n) {
this.n = n;
}
@Override
protected Integer compute() {
if (n <= 1) {
return 1;
}
MyRecursiveTask task1 = new MyRecursiveTask(n - 1);
task1.fork(); // task1을 병렬로 실행
return n * task1.join(); // 결과를 합쳐서 반환
}
}
설명:
- ForkJoinPool pool = new ForkJoinPool(); - ForkJoinPool 인스턴스를 생성하여, 작업을 이 풀에 제출할 수 있습니다.
- RecursiveTask task = new MyRecursiveTask(10); - RecursiveTask를 상속한 작업을 정의. 여기서는 1부터 10까지의 팩토리얼을 계산하는 예시입니다.
- task.fork(); - fork() 메서드를 사용하여 작업을 병렬로 실행합니다.
- task.join(); - join() 메서드를 사용하여 작업의 결과를 기다리며 받습니다.
5️⃣ ForkJoinPool 주요 메서드
- fork(): 작업을 비동기적으로 실행시킵니다. 작업이 즉시 시작되며, 현재 스레드는 계속 실행됩니다.
- join(): 작업이 완료될 때까지 결과를 기다립니다. fork()로 실행된 작업의 결과를 기다리며 받습니다.
- invoke(): fork()와 join()을 결합한 메서드입니다. 작업을 제출하고, 결과가 반환될 때까지 기다립니다.
6️⃣ ForkJoinPool 장점
✅ 성능 최적화
CPU 코어를 효율적으로 활용하여 병렬 처리가 가능합니다. 작은 작업을 분할하고 동시에 처리하여 성능 향상.
✅ 동적 스레드 관리
동적 스레드 할당 및 작업 큐 관리가 효율적으로 이루어집니다. 필요시 다른 스레드가 대기 중인 작업을 처리할 수 있습니다.
✅ 스케일링 (Scale-out)
ForkJoinPool은 스레드 풀의 크기를 동적으로 조정하면서 여러 작업을 처리할 수 있기 때문에, 대규모 데이터 처리 및 병렬 작업에서 강력한 성능을 발휘합니다.
7️⃣ ForkJoinPool 사용 예시
팩토리얼 계산 예시 (재귀적으로 분할)
import java.util.concurrent.*;
public class ForkJoinExample {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
FactorialTask task = new FactorialTask(10);
Integer result = pool.invoke(task);
System.out.println("Factorial of 10 is: " + result);
}
}
class FactorialTask extends RecursiveTask {
private final int n;
FactorialTask(int n) {
this.n = n;
}
@Override
protected Integer compute() {
if (n == 1) {
return 1;
} else {
FactorialTask subTask = new FactorialTask(n - 1);
subTask.fork(); // 재귀적으로 작업 분할
return n * subTask.join(); // 하위 작업의 결과를 합산
}
}
}
8️⃣ 결론
ForkJoinPool은 병렬 처리에 최적화된 Java 클래스입니다. 작은 작업들을 병렬로 처리하여 성능을 극대화합니다. 특히 대규모 데이터 처리나 분할-정복 알고리즘을 사용할 때 매우 유용하며, 동적 스레드 관리와 작업 큐 최적화 덕분에 효율적으로 멀티스레딩 작업을 처리할 수 있습니다.
'[개발관련] > JAVA' 카테고리의 다른 글
JAVA의 람다식(Lambda Expression) 이해하기 (1) | 2025.02.01 |
---|---|
CountDownLatch와 쓰레드 협업용 동기화 도구 (4) | 2025.01.18 |
[이펙티브 자바] 아이템 12. toString을 항상 재정의하라 (0) | 2023.10.20 |
[이펙티브 자바] 아이템 11. equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2023.10.18 |
[이펙티브 자바] 아이템 09. try-finally보다는 try-with-resources를 사용하라. (0) | 2023.10.17 |