본문 바로가기
Java

자바 비동기 CompletableFuture, ForkJoinPool의 개요 및 사용법

by 긴소리 2024. 12. 31.
728x90
반응형

 

CompletableFuture와 ForkJoinPool의 개요 및 사용법


1. CompletableFuture

CompletableFuture는 비동기 프로그래밍을 위해 자바에서 제공하는 고수준의 비동기 처리 API입니다. 이는 결과가 완전히 처리되기 전까지 계속 진행 중인 작업을 나타냅니다. 즉, 특정 작업이 완료되면 추가 작업이 수행되도록 설정할 수 있는 비동기 체인 방식의 메커니즘입니다.


CompletableFuture 주요 특징:

  • 비동기 작업: 작업이 완료되기 전에 다음 작업을 수행하도록 설정할 수 있습니다. 예를 들어, 하나의 작업이 끝나면 그 결과를 기반으로 추가 작업이 실행되게 합니다.
  • 멀티스레드: 여러 작업을 병렬로 실행할 수 있도록 지원합니다.
  • 결과 제공: 작업이 완료된 후 결과를 반환하며, 이 과정에서 예외 처리가 함께 수행됩니다.

 

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 비동기 작업
            return "Task Completed";
        });

        future.thenApply(result -> {
            // 첫 번째 작업 후 처리
            return result + " and processed further";
        }).thenAccept(finalResult -> {
            // 최종 작업 결과 출력
            System.out.println(finalResult);
        });
    }
}

 

 

주요 메서드:

  • supplyAsync(Supplier<U> supplier): 비동기 작업을 시작하고, 그 결과를 제공.
  • thenApply(Function<T, U> fn): 첫 번째 작업이 끝난 후 추가 작업을 수행.
  • thenAccept(Consumer<T> action): 최종적으로 처리를 완료할 때 호출.

2. ForkJoinPool

ForkJoinPool은 병렬 처리를 위해 자바에서 제공하는 스레드 풀의 일종으로, 대량의 병렬 작업을 효율적으로 처리할 수 있도록 설계되었습니다. 주로 작업이 분할되어 각 부분이 독립적으로 실행되고, 최종 결과가 결합되는 방식으로 처리됩니다.


ForkJoinPool 주요 특징:

  • 분할 정복 패턴: 작업이 여러 개의 작은 작업으로 나뉘며, 각 작은 작업은 병렬로 실행됩니다.
    작업이 끝난 후 최종 결과가 결합됩니다.
  • 작업 크기 조정: 대규모 작업을 작은 작업 단위로 나눠 처리하여 성능을 향상시킵니다.
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class ForkJoinPoolExample {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        long result = pool.invoke(new SumTask(1, 10000));
        System.out.println("Sum: " + result);
    }
}

class SumTask extends RecursiveTask<Long> {
    private final int start, end;

    public SumTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if (end - start <= 10) {
            long sum = 0;
            for (int i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            int middle = (start + end) / 2;
            SumTask leftTask = new SumTask(start, middle);
            SumTask rightTask = new SumTask(middle + 1, end);
            leftTask.fork();       // 왼쪽 작업 병렬 처리
            rightTask.fork();      // 오른쪽 작업 병렬 처리
            return leftTask.join() + rightTask.join();  // 결과 결합
        }
    }
}

 

주요 메서드:

  • fork(): 새로운 서브 작업으로 나눠 병렬로 실행.
  • join(): 서브 작업이 완료될 때까지 기다렸다가 최종 결과를 합침.
  •  

차이점 비교

특징 CompletableFuture ForkJoinPool
목적 비동기 작업 관리 및 체인으로 연결하는 작업 수행 병렬 처리를 위한 분할 정복 패턴 사용
사용 주제 소규모 비동기 작업 체인을 통해 다양한 작업 연속 수행 대량의 병렬 작업을 효율적으로 분할하고 처리
비동기 작업 작업이 완료되기 전 다음 작업 수행 가능 큰 데이터 집합을 여러 서브 작업으로 나눠 병렬로 처리
결과 반환 작업이 완료되면 즉시 결과 반환 병렬로 실행된 각 서브 작업의 결과를 합산하여 반환

결론:

  • CompletableFuture는 비동기 작업 체인을 통해 작업을 쉽게 연결할 수 있으며, 이벤트 기반 또는 체인 기반의 동작이 필요한 경우 유리합니다.
  • ForkJoinPool은 대규모 병렬 작업을 효율적으로 처리하며, 분할 정복 패턴을 통해 처리할 때 성능이 크게 향상됩니다.

 

CompletableFuture와 ForkJoinPool 결합 사용의 장점

  • 병렬 작업 처리: ForkJoinPool은 대규모 작업을 병렬로 처리하는 데 적합하며, CompletableFuture는 각 작업의 결과를 비동기적으로 관리합니다. 두 기술을 결합하면 큰 작업 단위를 여러 소작업으로 나누어 처리할 수 있습니다.
  • 비동기 체인의 연결: CompletableFuture는 작업이 완료되면 추가 작업을 실행하는 비동기 체인을 통해 후속 작업을 실행할 수 있습니다. ForkJoinPool은 이러한 작은 작업들을 병렬로 실행하는 데 유리합니다.
  • 성능 향상: 대량의 작업을 분할하고, 분산 처리하기 때문에 처리 속도가 향상됩니다. 예를 들어, 대규모 데이터를 병렬로 처리해야 할 때 큰 효과를 발휘합니다.
  •  
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class CombinedExample {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
            return pool.invoke(new SumTask(1, 10000));
        });

        future.thenApply(result -> {
            return result + 1000; // 후속 작업 추가
        }).thenAccept(finalResult -> {
            System.out.println("Final Result: " + finalResult);
        });
    }
}

class SumTask extends RecursiveTask<Long> {
    private final int start, end;

    public SumTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if (end - start <= 10) {
            long sum = 0;
            for (int i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            int middle = (start + end) / 2;
            SumTask leftTask = new SumTask(start, middle);
            SumTask rightTask = new SumTask(middle + 1, end);
            leftTask.fork();
            rightTask.fork();
            return leftTask.join() + rightTask.join();
        }
    }
}
반응형

댓글