Programming/Java

Java - Future Interface 비동기적 연산 작업을 위한 인터페이스

Jan92 2022. 4. 4. 23:32
반응형

Future Interface

Java에서 비동기적 연산 작업을 위해 만들어진 인터페이스인 Future에 대해서 살펴봅니다.

 

 

Future란,

자바 1.5에서 나온 인터페이스로 비동기적 연산의 처리 결과를 표현하기 위해 사용됩니다. 비동기 처리가 완료되었는지 확인하고, 처리 완료를 기다리고, 처리 결과를 반환하는 메서드를 제공합니다.

Future를 이용하면 멀티 스레드 환경에서 처리된 어떤 데이터를 다른 스레드에 전달할 수 있으며, Future는 내부적으로 Thread-Safe 하게 구현되어 있기 때문에 synchronized block(동기화 블록)을 사용하지 않아도 됩니다.

Future 객체는 작업이 완료될 때까지 기다렸다가 최종 결과를 얻는 데 사용하며, 때문에 지연 완료(pending completion) 객체라고도 합니다.

 

 

***

synchronized block(동기화 블록)은 같은 객체에 대한 모든 동기화 블록은 한 시점에 오직 한 Thread만이 블록 안으로 접근하도록(실행하도록)하는 것입니다.

 

 

 


 

 

 

Future Interface의 메서드

public interface Future<V> {
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
    boolean isCancelled();
    boolean isDone();
    boolean cancel(boolean mayInterruptIfRunning)
}

이어서 제공되는 메서드들을 살펴보겠습니다.

 

get()

작업된 결과를 가지고 오는 메서드입니다. 만약 결과가 아직 리턴되지 않았다면 결과가 나올 때까지 기다립니다. 이때 중요한 점은 결과가 반환되기 전까지 애플리케이션의 진행을 'block' 한다는 것입니다.

 

get(Long time, TimeUnit unit)

위에서 본 것처럼 get() 메서드는 결과가 반환될 때까지 애플리케이션의 진행을 'block' 시킵니다. 때문에 만약 작업의 결과가 반환되지 않는다면 get() 메서드를 호출한 Thread는 무한히 대기하게 되고, 프로그램은 응답이 없는 상태가 됩니다.

그래서 Timeout을 설정하는 해당 메서드를 통해 일정 시간 내에 응답이 없으면 'TimeoutException' 예외를 발생할 수 있으며, 이때는 try-catch로 예외 처리가 필요합니다.

 

isDone(), isCancelled()

현재 작업(Task)의 상태를 알 수 있으며, 일반적으로 get()과 isDone(), cancel()과 isCancelled()를 짝지어서 사용합니다.

isDone() 메서드의 경우 해당 Task가 완료되었을 경우에 true를 반환합니다. 여기서 말하는 완료에는 정상적인 동작 완료, 예외, 취소 등이 있고, 해당되는 모든 경우 true를 반환합니다.

isCancelled() 메서드의 경우 해당 Task가 정상적으로 완료되기 전에 삭제된 경우 true를 반환합니다.

 

cancel()

현재 작업(Task)의 중단을 시도하는 메서드입니다. 하지만 Task가 이미 완료되었다면 cancel() 메서드는 동작하지 않습니다.

Task의 동작을 취소했으면 true, 취소하지 못했다면 false를 리턴합니다.

parameter로 true를 전달하면 현재 진행 중인 스레드를 interrupt 하고, 그렇지 않으면 현재 진행 중인 작업이 끝날 때까지 기다립니다.

 

 

 


 

 

 

기본적인 사용 예시

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //ExecutorService 초기화
        ExecutorService executor = Executors.newSingleThreadExecutor();

        //Callable Task 생성
        Callable<String> callableTask = () -> {
            System.out.println(LocalTime.now() + " Task Start");
            Thread.sleep(1000L);
            return "Task Result";
        };

        //submit() 메서드로 Task 실행
        Future<String> future = executor.submit(callableTask);

        System.out.println(LocalTime.now() + " Waiting the task done");
        System.out.println("isDone 1 = " + future.isDone());
        String result = future.get();  //Task 결과 대기
        System.out.println("isDone 2 = " + future.isDone());
        System.out.println(LocalTime.now() + " future.get() = " + result);
    }

Future의 가장 기본적인 사용 예시입니다.

 

여기서 'ExecutorService'는 작업의 효율적인 병렬 처리를 위해 제공되는 Java 라이브러리이며, 코드에서 보는 것처럼 ExecutorService를 초기화하고 원하는 작업(Task)을 할당해야 하는데, 이때 Task는 Callable 또는 Runnable 인터페이스를 구현하여 생성합니다.

결괏값이 없는 작업일 경우 Runnable을 사용하고, 결괏값이 있는 작업일 경우 Callable을 사용하는데, 결과가 리턴되어야 하는 경우가 많기 때문에 주로 Callable을 구현한 Task를 인자로 사용합니다.

 

이어서 ExecutorService의 submit() 메서드로 Task를 실행합니다. submit() 메서드가 호출되었을 때 Future 객체는 바로 리턴되지만, 값이 설정되지 않은 상태입니다.

그리고 아래 future.get()은 Future 객체에 어떤 값이 설정될 때까지 기다립니다. submit()에 전달된 Callable이 어떤 값을 리턴하면 그 값을 Future에 설정합니다.

 

* isDone() 메서드를 get() 앞 뒤에 배치하여 작업의 종료 여부를 확인하였습니다.

 

 

//실행 결과
23:09:32.917 Waiting the task done
isDone 1 = false
23:09:32.918 Task Start
isDone 2 = true
23:09:33.922 future.get() = Task Result

(실행 결과)

 

 

 


 

 

 

Timeout 예시

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

        Callable<String> callableTask = () -> {
            System.out.println(LocalTime.now() + " Task Start");
            Thread.sleep(2000L);
            System.out.println(LocalTime.now() + " Task End");
            return "Task Result";
        };

        Future<String> future = executor.submit(callableTask);

        System.out.println(LocalTime.now() + " Waiting the task done");
        String result = null;
        try {
            result = future.get(1000, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            System.out.println(LocalTime.now() + " TimeoutException 발생");
        }
        System.out.println(LocalTime.now() + " future.get() = " + result);
    }

기본적인 동작 원리는 같으며, Task에 걸리는 시간을 2초로 주고, Future의 get() 메서드에 timeout 시간을 1초로 설정한 예시입니다.

 

//실행 결과
23:03:39.459 Task Start
23:03:39.461 Waiting the task done
23:03:40.462 TimeoutException 발생
23:03:40.462 future.get() = null
23:03:41.464 Task End

(실행 결과)

 

 

 


 

 

Cancel 예시

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

        Callable<String> callableTask = () -> {
            System.out.println(LocalTime.now() + " Task Start");
            Thread.sleep(2000L);
            return "Task Result";
        };

        Future<String> future = executor.submit(callableTask);
        Thread.sleep(1000L);
        boolean cancelResult = future.cancel(true);
        System.out.println("cancelResult = " + cancelResult);
        System.out.println("isCancelled = " + future.isCancelled());

        String result = future.get();  //Task 결과 대기
        System.out.println(LocalTime.now() + " future.get() = " + result);
    }

cancel() 메서드의 예시입니다.

아래 결과와 같이 future.get() 부분에서 'CancellationException' 예외가 발생하기 때문에 TimeoutException과 마찬가지로 try-catch 처리를 해줄 수 있으며, CancellationException은 작업이 취소되었기 때문에 FutureTask의 작업 결과를 가져올 수 없을 때 발생하는 예외입니다.

 

Exception indicating that the result of a value-producing task, such as a FutureTask, cannot be retrieved because the task was cancelled.

 

 

//실행 결과
23:17:50.616 Task Start
cancelResult = true
isCancelled = true
Exception in thread "main" java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)

(실행 결과)

 

 

 


 

 

 

CompletableFuture

Java 8에서는 Future에서 처리하기 힘든 비동기 작업을 위해 CompletableFuture가 새로 등장했는데요.

비동기로 처리할 작업이 두 가지 이상이며, 작업들 간의 의존성이 존재할 경우 Future만으로 처리하기가 어려워집니다. 이럴 경우에 CompletableFuture을 사용해서 해결할 수 있으며, CompletableFuture를 이용하면 비동기 처리 중 발생한 Exception을 전달하는 것도 가능하게 됩니다.

CompletableFuture에 대한 포스팅도 추후 정리해서 올리도록 하겠습니다.

 

 

 

< 참고 자료 >

 

Java - Future 사용 방법

Future는 비동기적인 연산의 결과를 표현하는 클래스입니다. 즉, 멀티쓰레드 환경에서 처리된 어떤 데이터를 다른 쓰레드에게 전달하는 역할을 합니다. Future 내부적으로 Thread-Safe 하도록 구현되

codechacha.com

 

 

CompletableFuture - 안정적 비동기 프로그래밍에 대해 - (1)

CompletableFuture - 안정적 비동기 프로그래밍 모든 코드는 깃허브에 있습니다. https://github.com/JunHoPark93/completablefuture-practice JunHoPark93/completablefuture-practice Contribute to JunHoPark93..

pjh3749.tistory.com

반응형