ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • java.util.concurrent.Future
    JAVA 2025. 8. 7. 18:07
    728x90
    반응형

    1. Future란 무엇일까?

    Java의 Future Interface는 비동기 연산의 결과를 나타내는 핵심 컴포넌트입니다.

    Future는 Multi Thread 환경에서 비동기적으로 실행되는 작업의 결과를 관리하기 쉽게 해줍니다.

    • 시간이 오래 걸리는 작업을 Background에서 실행 하는 경우
    • 여러 작업을 병렬로 실행하고 결과를 기다리는 경우
    • 작업의 진행 상태를 확인하거나 취소해야 하는 경우

    2. Future의 기본 개념

    2-1. 비동기 연산의 생명 주기

    Future로 관리하는 비동기 작업은 다음과 같이 나타낼 수 있습니다.

    생성 -> 실행 -> 완료 / 취소 / 예외

    2-2. Generic Type

    Future는 Generic Interface이기 때문에 반환될 결과의 타입을 지정할 수 있습니다.

    Future<String> stringResult; // 문자열을 반환하는 Future
    Future<Integer> intResult;   // 정수를 반환하는 Future
    Future<Void> nullResult;     // 결과 값이 없는 비동기 작업 (get()은 null 반환)
    Future<?> wildcardResult;    // 특정할 수 없는 타입의 결과를 반환하는 Future

    3. Future 주요 메소드

    Future Interface에는 5개의 핵심 메소드를 제공합니다.

    3-1. 결과 조회

    // 비동기 작업이 완료될 때까지 기다렸다가 결과를 반환합니다.
    V get() throws InterruptedException, ExecutionException
    
    // 비동기 작업이 완료될 때까지 최대 timeout 시간만큼 기다린 후 결과를 반환합니다.
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException

    3-2. 상태 확인

    // 작업이 완료되기 전 cancel 메소드로 취소되었다면 true를 반환합니다.
    boolean isCancelled()
    
    // 작업이 정상 종료, 예외, 취소로 인해 완료되었다면 true를 반환합니다.
    boolean isDone()

    3-3. 작업 취소

    // 작업을 취소하려고 시도하는 메서드입니다.
    // 작업이 이미 완료되었거나 이미 취소된 경우에는 false를 반환합니다.
    // 작업이 실행 중이며 mayInterruptIfRunning이 true이면 실행 중인 스레드를 interrupt하여 중단을 시도합니다.
    // 취소에 성공하면 true, 실패하면 false를 반환합니다.
    boolean cancel(boolean mayInterruptIfRunning);

    4. 기초 예제

    4-1. 기본적인 Future 사용 예제

    ExecutorService executor = Executors.newSingleThreadExecutor();
    
    Future<String> future = executor.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            Thread.sleep(2000); // 2초 작업 시뮬레이션
            return "작업 완료!";
        }
    });
    
    System.out.println("작업 시작...");
    
    try {
        String result = future.get();
        System.out.println("결과: " + result);
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    
    executor.shutdown();
    
    // 작업 시작...
    // 결과: 작업 완료!

    4-2. Lamda 표현식을 사용한 예제

    ExecutorService executor = Executors.newSingleThreadExecutor();
            
    Future<String> future = executor.submit(() -> {
        Thread.sleep(1000);
        return "치킨 + 피자";
    });
    
    // 🎉 isDone 메소드가 논블로킹이라는걸 확인할 수 있습니다.
    while (!future.isDone()) {
        System.out.println("아직 다이어트 중...");
        Thread.sleep(500);
    }
            
    try {
        String result = future.get();
        System.out.println("저녁 메뉴는 " + result + "입니다.");
    } catch (Exception e) {
        e.printStackTrace();
    }
            
    executor.shutdown();
    
    // 아직 다이어트 중...
    // 아직 다이어트 중...
    // 아직 다이어트 중...
    // 저녁 메뉴는 치킨 + 피자입니다.

    5. ExecutorService와 Future

    5-1. ExecutorService를 활용한 Future 반환 예제

    ExecutorService executor = Executors.newFixedThreadPool(3);
    
    // Callable: 작업이 직접 결과를 반환 (Future<String>)
    Future<String> callableResult = executor.submit(() -> {
        return "Callable 결과";
    });
    
    // Runnable + 결과값: 작업은 결과 없이 실행, 지정된 값 반환 (Future<String>)
    Future<String> runnableResult = executor.submit(() -> {
        System.out.println("Runnable 실행");
    }, "Runnable 완료");
    
    // Runnable만: 결과 값 없음, get()은 null 반환 (Future<?>)
    Future<?> voidResult = executor.submit(() -> {
        System.out.println("결과 없는 작업");
    });
    
    try {
        System.out.println(callableResult.get()); // 출력: Callable 결과
        System.out.println(runnableResult.get()); // 출력: Runnable 완료
        if (voidResult.get() == null) {
            System.out.println("null 반환"); // 출력: null 반환
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    executor.shutdown();
    
    // submit() 호출 시 작업을 스레드 풀의 작업 큐에 추가합니다.
    // submit() 자체는 즉시 Future 객체를 반환하고 실제 작업의 실행은 스레드 풀의 스케줄링에 따라 비동기적으로 처리됩니다.
    // 즉, 출력은 스레드 풀이 작업을 처리하는 시점에서 발생합니다.
    
    // Runnable 실행
    // Callable 결과
    // Runnable 완료
    // 결과 없는 작업
    // null 반환

    5-2. 병렬 처리 예제

    ExecutorService executor = Executors.newFixedThreadPool(3);
    List<Future<Integer>> futures = new ArrayList<>();
    	        
    // 여러 작업을 병렬로 시작
    for (int i = 1; i <= 5; i++) {
        final int taskId = i;
        // 병렬적으로 3개의 스레드로 5번 작업을 합니다.
        Future<Integer> future = executor.submit(() -> {
            System.out.println("작업 >> " + taskId + " 시작됨 (소요시간: " + taskId + "초)");
            Thread.sleep(taskId * 1000); 
            return taskId * taskId;
        });
        futures.add(future);
    }
    	        
    // 모든 결과 수집
    System.out.println("모든 작업 결과:");
    for (int i = 0; i < futures.size(); i++) {
        try {
            // Future의 get() 메소드는 블로킹이라는 걸 확인할 수 있습니다.
            Integer result = futures.get(i).get();
            System.out.println("작업 " + (i + 1) + " 결과: " + result);
        } catch (Exception e) {
            System.out.println("작업 " + (i + 1) + " 실패: " + e.getMessage());
        }
    }
    	        
    executor.shutdown();
    
    // 모든 작업 결과:
    // 작업 >> 3 시작됨 (소요시간: 3초)
    // 작업 >> 1 시작됨 (소요시간: 1초)
    // 작업 >> 2 시작됨 (소요시간: 2초)
    // 작업 >> 4 시작됨 (소요시간: 4초)
    // 작업 1 결과: 1
    // 작업 >> 5 시작됨 (소요시간: 5초)
    // 작업 2 결과: 4
    // 작업 3 결과: 9
    // 작업 4 결과: 16
    // 작업 5 결과: 25

    6. FutureTask

    FutureTask는 Future Interface와 Runnable Interface를 구현합니다.

    6-1. FutureTask 기본 사용 예제

    FutureTask는 한번 실행되면 상태가 완료 or 취소로 결정되기 때문에 재 사용할 수 없습니다.

    즉, 아래 예제를 섞는 경우 두번째 작업은 무시하게 됩니다.

    // FutureTask 생성
    FutureTask<String> futureTask = new FutureTask<>(() -> {
        System.out.println("작업 실행...");
        Thread.sleep(2000);
        return "FutureTask 완료";
    });
    
    Thread thread = new Thread(futureTask);
    thread.start();
    
    try {
        String result = futureTask.get();
        System.out.println(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    // -----------------------------------------
    
    // FutureTask 생성
    FutureTask<String> futureTask = new FutureTask<>(() -> {
        System.out.println("작업 실행...");
        Thread.sleep(2000);
        return "FutureTask 완료";
    });
    
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(futureTask);
    
    try {
        String result = futureTask.get();
        System.out.println(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    executor.shutdown();

    6-3. FutureTask 커스터마이징

    public class FutureTaskCustom<V> extends FutureTask<V> {
    
        private final String taskName;
    
        public FutureTaskCustom(Callable<V> callable, String taskName) {
            super(callable);
            this.taskName = taskName;
        }
        
        @Override
        // 작업이 완료되면 FutureTask가 호출하는 Callback 메소드 입니다.
        // FutureTask 내부에서 이미 정의되어있습니다.
        protected void done() {
            if (isCancelled()) {
                System.out.println("==> " + taskName + " [취소됨]");
            } else {
                try {
                    get(); 
                    System.out.println("==> " + taskName + " [성공적으로 완료됨]");
                } catch (Exception e) {
                    System.out.println("==> " + taskName + " [예외 발생으로 완료됨]");
                }
            }
        }
    }
    
    // ---------------------
    
    TTTs<Integer> task = new TTTs<>(() -> {
        Thread.sleep(1000);
        return 100;
    }, "계산 작업");
            
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.execute(task);
            
    try {
        Integer result = task.get();
        System.out.println("결과: " + result);
    } catch (Exception e) {
        e.printStackTrace();
    }
            
    executor.shutdown();
    
    ==> 계산 작업 [성공적으로 완료됨]
    결과: 100

    7. 예외처리

    7-1. ExecutionException 처리

    ExecutorService executor = Executors.newSingleThreadExecutor();
    
    Future<String> future = executor.submit(() -> {
        if (Math.random() > 0.5) {
            throw new RuntimeException("무작위 오류 발생!");
        }
    	return "성공!";
    });
    
    try {
        // 예외발생시 ExecutionException으로 감싸서 전달합니다.
        String result = future.get();
        System.out.println("결과: " + result);
    } catch (ExecutionException e) {
        // ExecutionException에서 실제 Exception을 가져옵니다.
        Throwable cause = e.getCause();
        // 실제 원본 메시지 추출합니다.
        System.out.println("작업 중 예외 발생: " + cause.getMessage());
    } catch (InterruptedException e) {
        System.out.println("대기 중 인터럽트 발생");
        // InterruptedException을 catch하면 현재 스레드의 인터럽트 상태는 false로 초기화됩니다.
        // 현재 메서드에서는 인터럽트를 완전히 처리할 수 없으므로 이 스레드를 호출한 상위 코드(호출한 곳)가 인터럽트 발생 사실을 알수 있도록 인터럽트 상태를 설정(true)해주는 것 입니다.
        Thread.currentThread().interrupt();
    }
    
    executor.shutdown();

    상위 코드에서 Thread.currentThread().isInterrupted()를 통해 Interrupt 상태를 확인할 수 있으며 true라면 실제로 종료된 것입니다.

    7-3. 예외 래핑

    ExecutorService executor = Executors.newSingleThreadExecutor();
    
    Future<String> future = executor.submit(() -> {
        try {
            Thread.sleep(1000);
            throw new IOException("파일을 찾을 수 없습니다");
        } catch (IOException e) {
            // IOException을 RuntimeException으로 감싸서 전달합니다.
            throw new RuntimeException(e);
        }
    });
    
    try {
        future.get();
    } catch (ExecutionException e) {
        //  e.getCause()를 호출하면 작업 스레드가 던졌던 RuntimeException이 나옵니다.
        Throwable cause = e.getCause();
        if (cause instanceof RuntimeException && cause.getCause() instanceof IOException) {
            // RuntimeException 안에 있던 진짜 원인(IOException)을 추출합니다.
            IOException originalException = (IOException) cause.getCause();
            System.out.println("IO 예외: " + originalException.getMessage());
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    
    executor.shutdown();

    8. 다양한 예제

    8-1. Timeout 취소

    ExecutorService executor = Executors.newSingleThreadExecutor();
    
    Future<String> future = executor.submit(() -> {
        Thread.sleep(5000);
        return "늦은 결과";
    });
    
    try {
        String result = future.get(3, TimeUnit.SECONDS);
        System.out.println("결과: " + result);
    } catch (TimeoutException e) {
        System.out.println("시간 초과! 작업을 취소합니다.");
        boolean cancelled = future.cancel(true);
        System.out.println("취소 성공: " + cancelled);
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    executor.shutdown();

    8-2. 취소

    ExecutorService executor = Executors.newSingleThreadExecutor();
    
    Future<String> future = executor.submit(() -> {
        for (int i = 0; i < 10; i++) {
        	// 인터럽트 상태를 직접 확인하는 부분
        	if (Thread.currentThread().isInterrupted()) {
                System.out.println("작업이 인터럽트되었습니다.");
                return "인터럽트됨";
            }
    
            try {
                Thread.sleep(1000); // InterruptedException 발생 가능
            } catch (InterruptedException e) {
                System.out.println("슬립 중 인터럽트 감지");
                Thread.currentThread().interrupt(); // 인터럽트 상태 복원
                return "인터럽트됨";
            }
    
            System.out.println("작업 진행 중... " + i);
        }
        return "완료됨";
    });
    
    Thread.sleep(2000);
    // Interrupt를 발생시킵니다.
    boolean cancelled = future.cancel(true);
    System.out.println("취소 요청 결과: " + cancelled);
    
    try {
        String result = future.get();
        System.out.println("최종 결과: " + result);
    } catch (CancellationException e) {
        System.out.println("작업이 취소되었습니다.");
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    executor.shutdown();

    9. 실제 예제

    9-1. 웹크롤링 병렬 예제

    public class WebCrawlerExample {
        private final ExecutorService executor = Executors.newFixedThreadPool(5);
    
        public void crawlUrls(List<String> urls) {
            List<Future<CrawlResult>> futures = new ArrayList<>();
    
            // 모든 크롤링 작업을 스레드 풀에 전달합니다.
            // submit은 작업을 맡기자마자 바로 다음 코드를 실행하므로 모든 작업이 거의 동시에 시작됩니다.
            System.out.println("모든 URL에 대한 크롤링 작업을 동시에 제출합니다...");
            for (String url : urls) {
                Future<CrawlResult> future = executor.submit(() -> crawlSingleUrl(url));
                futures.add(future);
            }
    
            // 전달된 작업들의 결과를 순서대로 확인합니다.
            System.out.println("각 작업의 결과를 확인합니다 (최대 10초 대기)...");
            for (int i = 0; i < futures.size(); i++) {
                Future<CrawlResult> future = futures.get(i);
                String url = urls.get(i);
                try {
                    // future.get()을 호출하되, 최대 10초까지만 결과를 기다립니다.
                    CrawlResult result = future.get(10, TimeUnit.SECONDS);
                    System.out.println("✅ URL " + url + " 크롤링 성공: " + result);
                } catch (TimeoutException e) {
                    // 10초를 초과하면 TimeoutException이 발생합니다.
                    System.out.println("❌ URL " + url + " 작업 시간 초과 (타임아웃)");
                    // 타임아웃된 작업에게 더 이상 실행할 필요가 없다고 인터럽트 신호를 보냅니다.
                    future.cancel(true);
                } catch (Exception e) {
                    // 그 외 작업 실행 중 발생한 예외 처리
                    System.out.println("🔥 URL " + url + " 처리 중 오류 발생: " + e.getMessage());
                }
            }
    
            executor.shutdown();
        }
    
        // 단일 URL을 크롤링하는 메서드 (시뮬레이션)
        private CrawlResult crawlSingleUrl(String url) throws InterruptedException {
            System.out.println("... " + url + " 크롤링 시작");
            Thread.sleep((long) (Math.random() * 12000));
            return new CrawlResult(url, "크롤링 완료");
        }
    
        static class CrawlResult {
            final String url;
            final String content;
    
            CrawlResult(String url, String content) {
                this.url = url;
                this.content = content;
            }
    
            @Override
            public String toString() {
                return "CrawlResult{url='" + url + "', content='" + content + "'}";
            }
        }
    
        public static void main(String[] args) {
            WebCrawlerExample crawler = new WebCrawlerExample();
            List<String> urls = Arrays.asList(
                "https://www.google.com",
                "https://www.waug.com",
                "https://www.whitehouse.com",
                "https://www.naver.com",
                "https://www.daum.net"
            );
    
            crawler.crawlUrls(urls);
        }
    }

    10. Future 한계와 대안

    10-1. Future 한계점

     

    • 블로킹 방식: get() 메서드는 블로킹 방식으로만 동작합니다.
    • 콜백 부재: 완료 시 자동으로 실행될 콜백 메서드가 없습니다.
    • 조합 어려움: 여러 Future를 조합하기 복잡합니다.
    • 예외 처리: ExecutionException으로 래핑되어 처리가 복잡합니다.

    10-2. CompletableFuture(Java 8+)

    CompletableFuture<String> future = CompletableFuture
            // 별도의 스레드에서 실행 (ForkJoinPool의 공용 스레드)
            .supplyAsync(() -> {
                System.out.println("  [1] supplyAsync: 'Hello'를 생성 중... " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return "Hello";
            })
            .thenApply(result -> {
                System.out.println("  [2] thenApply: '" + result + "'에 ' World'를 붙이는 중..." + Thread.currentThread().getName());
                return result + " World";
            })
            // 순서에 맞게 위에 실행 후 실행되는 체이닝 메소드 입니다.
            .thenCompose(result -> {
                System.out.println("  [3] thenCompose: '" + result + "'에 '!'를 붙이는 새 작업을 시작..." + Thread.currentThread().getName());
                return CompletableFuture.supplyAsync(() -> result + "!"); // 결과: "Hello World!"
            })
            // 위에서 예외발생시 이 단계가 실행됩니다.
            .exceptionally(throwable -> {
                System.out.println("  [!] exceptionally: 오류 발생! " + throwable.getMessage());
                return "Error: " + throwable.getMessage();
            });
    
    // 논 블로킹 방식
    future.thenAccept(result -> {
        System.out.println("\n  [4] thenAccept (콜백): 최종 결과가 나왔습니다! -> " + result + " :: "  + Thread.currentThread().getName());
    });
    
    System.out.println("메인 스레드: 콜백을 등록했으니 다른 일을 계속할 수 있습니다." + Thread.currentThread().getName());
    
    // 블로킹 방식
    try {
        System.out.println("메인 스레드: 이제 get()으로 최종 결과가 올 때까지 기다립니다..." + Thread.currentThread().getName());
        String finalResult = future.get();
        System.out.println("  [5] get() (블로킹): 최종 결과를 받았습니다. -> " + finalResult + " :: " + Thread.currentThread().getName());
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    
    //   [1] supplyAsync: 'Hello'를 생성 중... ForkJoinPool.commonPool-worker-1
    // 메인 스레드: 콜백을 등록했으니 다른 일을 계속할 수 있습니다.main
    // 메인 스레드: 이제 get()으로 최종 결과가 올 때까지 기다립니다...main
    //   [2] thenApply: 'Hello'에 ' World'를 붙이는 중...ForkJoinPool.commonPool-worker-1
    //   [3] thenCompose: 'Hello World'에 '!'를 붙이는 새 작업을 시작...ForkJoinPool.commonPool-worker-1
    //   [4] thenAccept (콜백): 최종 결과가 나왔습니다! -> Hello World! :: ForkJoinPool.commonPool-worker-2
    //   [5] get() (블로킹): 최종 결과를 받았습니다. -> Hello World! :: main

     

    10-3. 2개의 Future 조합

    CompletableFuture<String> future1 = CompletableFuture
        .supplyAsync(() -> {
            System.out.println("야식 조회 중... " + Thread.currentThread().getName());
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            return "닭발";
        });
    
    CompletableFuture<Integer> future2 = CompletableFuture
        .supplyAsync(() -> {
            System.out.println("가격 조회 중... " + Thread.currentThread().getName());
            try { Thread.sleep(1500); } catch (InterruptedException e) {}
            return 900;
        });
    
    // 두 Future의 결과를 조합
    // 늦게 끝난 스레드가 해당 작업을 진행합니다.
    CompletableFuture<String> combined = future1.thenCombine(future2, (food, price) -> {
        System.out.println("결과 조합 중... " + Thread.currentThread().getName());
        return String.format("야식: %s, 가격: %d세", food, price);
    });
    
    try {
        String result = combined.get();
        System.out.println("최종 결과: " + result);
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    // 야식 조회 중... ForkJoinPool.commonPool-worker-1
    // 가격 조회 중... ForkJoinPool.commonPool-worker-2
    // 결과 조합 중... ForkJoinPool.commonPool-worker-1
    // 최종 결과: 이름: 닭발, 나이: 19000세

    10-4. 모든 Future 완료 대기

    // 병렬 처리
    CompletableFuture<String> userService = CompletableFuture
        .supplyAsync(() -> {
            System.out.println("사용자 서비스 호출...");
            try { Thread.sleep((long)(Math.random() * 2000)); } catch (InterruptedException e) {}
            return "사용자 데이터";
        });
    
    CompletableFuture<String> orderService = CompletableFuture
        .supplyAsync(() -> {
            System.out.println("주문 서비스 호출...");
            try { Thread.sleep((long)(Math.random() * 2000)); } catch (InterruptedException e) {}
            return "주문 데이터";
        });
    
    CompletableFuture<String> paymentService = CompletableFuture
        .supplyAsync(() -> {
            System.out.println("결제 서비스 호출...");
            try { Thread.sleep((long)(Math.random() * 2000)); } catch (InterruptedException e) {}
            return "결제 데이터";
        });
    
    // allOf는 전달된 모든 CompletableFuture가 완료되면 알려줍니다.
    // 단, 이건 이벤트이기 때문에 void로 써야합니다.
    CompletableFuture<Void> allTasks = CompletableFuture.allOf(
        userService, orderService, paymentService
    );
    
    // 3개 서비스가 모두 완료되면 이 블록이 실행됩니다.
    allTasks.thenRun(() -> {
        try {
            // 이 시점에는 모든 Future가 완료되었기 때문에 get()을 호출해도 기다림 없이 즉시 결과를 가져올 수 있습니다.
            String user = userService.get();
            String order = orderService.get();
            String payment = paymentService.get();
            
            System.out.println("=== 모든 데이터 수집 완료 ===");
            System.out.println("- " + user);
            System.out.println("- " + order);
            System.out.println("- " + payment);
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
    
    // 가장 오래 걸리는 작업이 1.5초이므로 시간 내에 완료될 것입니다.
    try {
        allTasks.get(5, TimeUnit.SECONDS);
        System.out.println("대시보드 로딩 완료!");
    } catch (Exception e) {
        System.out.println("타임아웃 또는 오류 발생: " + e.getMessage());
    }

    10-5. 가장 빠른 Future 반환

    CompletableFuture<String> server1 = CompletableFuture
        .supplyAsync(() -> {
            try { Thread.sleep((long)(Math.random() * 3000)); } catch (InterruptedException e) {}
            return "서버1 응답";
        });
    
    CompletableFuture<String> server2 = CompletableFuture
        .supplyAsync(() -> {
            try { Thread.sleep((long)(Math.random() * 3000)); } catch (InterruptedException e) {}
            return "서버2 응답";
        });
    
    CompletableFuture<String> server3 = CompletableFuture
        .supplyAsync(() -> {
            try { Thread.sleep((long)(Math.random() * 3000)); } catch (InterruptedException e) {}
            return "서버3 응답";
        });
    
    // allOf랑 다르게 무엇이든 하나가 완료되면 끝입니다.
    CompletableFuture<Object> fastest = CompletableFuture.anyOf(server1, server2, server3);
    
    try {
        Object result = fastest.get(4, TimeUnit.SECONDS);
        System.out.println("가장 빠른 응답: " + result);
        
        // 나머지 작업들은 interrupt를 발생시켜 종료 합니다
        server1.cancel(true);
        server2.cancel(true);
        server3.cancel(true);
        
    } catch (TimeoutException e) {
        System.out.println("모든 서버가 응답하지 않음");
    } catch (Exception e) {
        e.printStackTrace();
    }

     

    11. 비교

    특성 Future CompletableFuture
    실행 방식 블로킹 (get()) 논블로킹 콜백 지원
    예외 처리 ExecutionException 래핑 exceptionally() 메서드
    콜백 없음 thenAccept(), thenRun() 등
    여러 Future 조합 수동 처리 필요 allOf(), anyOf(), thenCombine()
    성능 스레드 블로킹 논 블로킹 + ForkJoinPool

     

    728x90
    반응형
Designed by Tistory.