프로그래밍/Java

CompletableFuture (3) - Callable 과 Future

개발정리 2022. 6. 14. 15:18

Thread는 Runnable과 Callable의 구현된 함수를 수행한다는 공통점이 있지만, 다음과 같은 차이점이 있다.

  • Runnable: 어떤 객체도 리턴하지 않습니다. Exception을 발생시키지 않습니다.
  • Callable: 특정 타입의 객체를 리턴합니다. Exception을 발생킬 수 있습니다.

 

Callable

  • 이전까지 사용했던 Runnable과 유사하지만 작업의 결과(return) 를 받을 수 있다.

Future

  • Future 는 자바 1.5 에 등장한 비동기 계산 결과를 나타내는 인터페이스
  • 비동기적인 작업의 현재 상태를 조회하거나 결과를 가져올 수 있다. 
  • Future를 이용하면 멀티쓰레드 환경에서 처리된 어떤 데이터를 다른 쓰레드에 전달할 수 있다.
  • Future 내부적으로 Thread-Safe 하도록 구현되었기 때문에 synchronized block을 사용하지 않아도 된다.
  • 비동기적인 작업을 수행?
    • 현재 진행하고 있는 Thread 가 아닌 별도의 Thread 에서 작업을 수행하는 것을 말한다.
    • 같은 Thread 에서 메서드를 호출할 때는 리턴 값을 받지만, 비동기적으로 작업을 수행할 때는 리턴 값을 전달받을 수 있는 무언가의 interface 가 필요한데 Future 가 그 역할을 한다.

 

 

Callable 과 Future 실습하기


결과를 가져오기 get()

  • 블록킹 콜이다.
  • 타임아웃(최대한으로 기다릴 시간)을 설정할 수 있다.

작업 상태 확인하기 isDone()

  •  완료 했으면 true 아니면 false를 리턴한다.

작업 취소하기 cancel()

  • 취소 했으면 true 못했으면 false를 리턴한다.
  • parameter true를 전달하면 현재 진행중인 쓰레드를 interrupt하고 그러지 않으면 현재 진행중인 작업이 끝날때까지 기다린다.
package com.example.reactivestreamstoby;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Main {
  public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newSingleThreadExecutor();

    Callable<String> hello = () -> {
      Thread.sleep(1000L);
      return "Hello";
    };

    System.out.println("start");

    // submit 시, Runnable 도 동일하게 Future 로 받을 수 있었다.
    // Callable 이 리턴하는 타입의 Future 를 받을 수 가 있게된다.
    Future<String> helloFuture = executorService.submit(hello);

    // 상태를 알고싶을때 상태에 따라 true/false 반환
    System.out.println(helloFuture.isDone()); 
    
    // Future 를 가지고  submit 이 만들어주는 값을 get 을 통해 꺼낼 수 있다.
    // get 이전까지는 코드가 계속해서 실행이 되지만, get 을 만나는 순간 멈춰서 결과값을 가져올때까지 기다린다. (블록킹 콜)
    helloFuture.get();

    // 작업 취소 기능, get() 을 할 수 없다.
    helloFuture.cancel(false);

    System.out.println("end");
    executorService.shutdown();
  }
}

 

 

 

여러 작업 동시에 실행하기 invokeAll()

  • 동시에 실행한 작업중에 제일 오래걸리는 작업 만큼 시간이 걸린다.
package com.example.reactivestreamstoby;

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
  public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newSingleThreadExecutor();

    Callable<String> hello = () -> {
      Thread.sleep(1000L);
      return "hello";
    };
    Callable<String> java = () -> {
      Thread.sleep(2000L);
      return "java";
    };
    Callable<String> hyokeun = () -> {
      Thread.sleep(3000L);
      return "hyokeun";
    };

    // invokeAll 은 1초, 2초, 3초 로 준 애들이 다 끝날때까지 기다린다. 즉, 자바가 끝날때까지 (3초) 기다리는것
    List<Future<String>> futures = executorService.invokeAll(Arrays.asList(hello, java, hyokeun));
    for (Future<String> f : futures) {
      System.out.println(f.get());
    }
    
    executorService.shutdown();
  }
}

 

여러 작업 중에 하나라도 먼저 응답이 오면 끝내기 invokeAny()

  • 동시에 실행한 작업중에 제일 짧게 걸리는 작업 만큼 시간이 걸린다.
  • 블록킹 콜이다.
package com.example.reactivestreamstoby;

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
  public static void main(String[] args) throws ExecutionException, InterruptedException {
    // 싱글쓰레드로 주면 맨 처음것만 나온다. 가장 먼저 처리되고 나머지는 큐에서 대기하고 있기 때문이다.
    ExecutorService executorService = Executors.newFixedThreadPool(4);

    Callable<String> hello = () -> {
      Thread.sleep(5000L);
      return "hello";
    };
    Callable<String> java = () -> {
      Thread.sleep(2000L);
      return "java";
    };
    Callable<String> hyokeun = () -> {
      Thread.sleep(3000L);
      return "hyokeun";
    };

    // 그런데 특정 서버 3대를 두고 모두 같은 값을 가져와야한다면 3대 모두를 기다릴 필요가 있을까? -> 이 경우에 해당되는데 InvokeAny
    // 여기선 가장 빠른 java (2초) 가 출력
    String s = executorService.invokeAny(Arrays.asList(hello, java, hyokeun));
    System.out.println(s);

    executorService.shutdown();
  }
}

 

 

 

참고


인프런 - 더 자바, Java8 (백기선)