Future<T>작업의 결과값을 미리 받고 원할 때 꺼낼 수 있게 해준다.
Future<Integer> future = es.submit(new ExCallable());으로 생성하며, submit을 통해 Callable을 통해 작업을 받는다. 그러고는 ExecutorService로 만들어진 스레드로 작업이 끝나면 Int Result = futurue.get()으로 결과값을 반환 받는다.
import java.util.concurrent.*; // Executor 관련 클래스들을 한 번에 임포트
public class FutureSimpleExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("--- Future 예제 시작 ---\n");
// 1. ExecutorService 생성
// 간단한 예제를 위해 단일 스레드 풀을 사용합니다.
ExecutorService executor = Executors.newSingleThreadExecutor();
System.out.println("ExecutorService (단일 스레드 풀) 생성됨.");
// 2. Callable 작업 정의
// 3초 후 String 값을 반환하는 Callable 작업을 생성합니다.
Callable<String> longRunningTask = () -> {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": 장시간 작업 시작...");
try {
TimeUnit.SECONDS.sleep(3); // 3초 동안 작업 수행을 시뮬레이션
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 인터럽트 상태 복원
System.err.println(threadName + ": 장시간 작업이 인터럽트되었습니다.");
return "작업 취소됨!"; // 인터럽트 시 반환할 값
}
System.out.println(threadName + ": 장시간 작업 완료.");
return "비동기 작업 결과 문자열입니다!"; // 작업 완료 후 반환할 실제 값
};
System.out.println("\n장시간 작업을 제출하고 Future 객체를 받습니다.");
// 3. Callable 작업을 제출하고 Future 객체를 얻습니다.
Future<String> futureResult = executor.submit(longRunningTask);
System.out.println("작업이 백그라운드에서 실행되는 동안 다른 작업을 수행할 수 있습니다.");
System.out.println("메인 스레드: 잠깐 다른 일 하기 (1초 대기)...");
TimeUnit.SECONDS.sleep(1); // 메인 스레드는 1초 동안 다른 작업 수행
System.out.println("메인 스레드: 작업 완료 여부 확인: " + futureResult.isDone()); // 아직 완료 안 됐을 것임
try {
System.out.println("메인 스레드: 비동기 작업의 결과를 기다리는 중 (get() 호출)...");
// 4. Future의 get() 메서드를 호출하여 작업 결과 가져오기
// 이 시점에서 작업이 완료될 때까지 메인 스레드는 블로킹됩니다.
String result = futureResult.get(); // 작업 완료를 기다리고 결과 반환
System.out.println("메인 스레드: 비동기 작업 결과: \"" + result + "\"");
System.out.println("메인 스레드: 작업 완료 여부 확인: " + futureResult.isDone()); // 이제 완료되었을 것임
} catch (InterruptedException e) {
System.err.println("메인 스레드: 작업 결과를 기다리는 중 인터럽트됨: " + e.getMessage());
} catch (ExecutionException e) {
// Callable의 call() 메서드에서 예외가 발생한 경우
System.err.println("메인 스레드: 비동기 작업 실행 중 예외 발생: " + e.getCause().getMessage());
} finally {
// 5. 스레드 풀 종료
executor.shutdown();
System.out.println("\n스레드 풀 종료 요청됨.");
// 모든 작업이 종료될 때까지 최대 5초 대기
if (executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("모든 작업이 성공적으로 종료되었습니다.");
} else {
System.out.println("일부 작업이 시간 내에 종료되지 않았습니다.");
}
}
System.out.println("\n--- Future 예제 종료 ---");
}
}
이렇게 함으로써 메인 스레드는 대기하지 않고 다음 작업을 이어 나간다. submit은 블로킹 메서드가 아니기 때문이다. 그러고 결과값을 받고 싶을 때 get을 사용해 값을 반환받으면 된다.
사용할 때는 submit과 get을 교차해서 사용하는 것은 좋지 않다.
그렇게 되면 싱글 스레드와 똑같기 때문이다.
작업을 받으면 작업을 감싼 채로 대신 BlockingQueue에 들어간다. 그 다음 스레드가 작업을 시작하고 만약 작업이 끝나기 전에 get을 호출하면 호출한 스레드를 대기시킨다. 그러고 작업이 끝나면 스레드를 깨우고 값을 반환한다.
작업이 끝난 후에 get을 호출했으면 바로 값을 반환한다.
Future에게 submit 해놓고 호출한 스레드는 다른 작업을 할 수 있다는 게 장점이다. 그러니까 여러 스레드에게 동시에 일을 시킬 수 있다는 것이다.
join이나 기존 스레드 환경에서는 스레드를 시작해도 스레드1 끝난 다음에 스레드2를 실행시켰기 때문에 사실상 싱글 스레드 환경과 똑같았다. 하지만 Future에게 작업 맡겨놓고 다른 작업을 수행시킴으로써 좀 더 성능이 향상되었다.
게다가 checkedException도 던질 수 있고 제네릭이라 유연하게 값을 반환할 수 있다.
작업을 취소한다. 작업이 취소되면 true, 작업이 완료되거나 취소할 수 없는 false을 반환한다.
그리고 true을 매개변수로 받을 경우 cancel을 샐행하지만, 실행중인 작업이 있으면 인터럽트로 작업을 끝낸다. 반대로 false일 경우 cancel을 실행하지만, 실행중인 작업을 끝내지는 않는다.
취소되었는데 get을 호출하면 CancellationException 런타임 예외가 발생한다.
취소가 되었는지 확인한다. 취소되었으면 true, 아니면 false을 반환한다.
작업이 완료되거나, 예외가 발생하였거나 취소되었거나 하면 true, 아니면 false을 반환한다.
Future 상태를 반환한다.
RUNNING
: 작업 실행 중
SUCCESS
: 성공 완료
FAILED
: 실패 완료
CANCELLED
: 취소 완료
작업 결과를 반환한다.
예외가 두 가지 발생한다. 대기시키는 블로킹 메서드이므로 InterruptedException하고 작업 계산 중에 발생하는 ExecutionException이 예외로 발생한다.
참고로 스레드를 대기시키는 블로킹 메서드다. 값 말고 예외도 받을 수 있다.
원인이 된 예외를 알고 싶으면 Throw cause = e.getCause()을 하면 된다.
get()과 똑같지만, 일정 시간이 지나면 예외를 발생시킨다.
get과 마찬가지로 두 가지의 예외와 함께, 주어진 시간 동안 작업이 완료되지 않을 때 발생하는 TimeoutException이 발생한다.