동시성 Java 프로그래밍에서 Future가 해결하는 문제는 무엇인가?

발행: (2026년 2월 24일 오전 01:15 GMT+9)
4 분 소요
원문: Dev.to

Source: Dev.to

Future가 해결하는 문제들

블로킹 문제

Future가 없으면 백그라운드 작업을 시작한 메인 스레드가 결과가 준비될 때까지 대기(블록)해야 하는 경우가 많아 UI나 다른 작업이 멈춥니다.

결과 문제

일반 Runnable은 “fire‑and‑forget” 형태라 값을 반환할 수 없습니다. FutureCallable을 함께 사용하면 작업이 결과(Integer, String 등)를 생성하고 나중에 가져올 수 있습니다.

제어 문제

때때로 작업이 더 이상 필요하지 않을 때가 있습니다. Futurecancel()isDone() 같은 메서드를 제공해 작업을 중단하거나 블록하지 않고 상태를 확인할 수 있게 합니다.

비동기 실행의 장점

  • 응답성 향상 – 메인 스레드는 백그라운드 작업이 진행되는 동안 계속 작업을 수행합니다.
  • 안전한 데이터 전달Future가 스레드 간 핸드쉐이크를 처리해 레이스 컨디션을 방지합니다.
  • 더 나은 제어 – 필요에 따라 작업을 조회, 취소하거나 타임아웃을 설정할 수 있습니다.

Java 21에서 Future 사용하기

예시: 기본 Future

import java.util.concurrent.*;

public class FutureBasics {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // try‑with‑resources automatically shuts down the executor (Java 21+)
        try (ExecutorService executor = Executors.newFixedThreadPool(1)) {

            System.out.println("Step 1: Submitting a long task...");

            // Future represents a result that will arrive later
            Future futureResult = executor.submit(() -> {
                Thread.sleep(2000); // Simulate heavy calculation
                return 42;
            });

            System.out.println("Step 2: Doing other work in the main thread...");

            // Step 3: Retrieve the result (blocks only if not finished)
            Integer result = futureResult.get();

            System.out.println("Step 4: The result is: " + result);
        }
    }
}

예시: 체이닝을 위한 CompletableFuture

import java.util.concurrent.CompletableFuture;

public class ModernAsyncExample {
    public static void main(String[] args) {
        System.out.println("Searching for a product...");

        CompletableFuture.supplyAsync(() -> {
            simulateDelay(1000);
            return "Gaming Laptop";
        })
        .thenApply(product -> "Found: " + product + " - $1200")
        .thenAccept(finalOutput -> System.out.println("Final Result: " + finalOutput))
        .join(); // Wait for completion in this simple demo

        System.out.println("App execution finished.");
    }

    private static void simulateDelay(int ms) {
        try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
    }
}

모범 사례

  • 항상 타임아웃을 사용하세요: future.get(5, TimeUnit.SECONDS)는 무한 블로킹을 방지합니다.
  • 예외를 처리하세요: 백그라운드에서 발생한 예외는 ExecutionException에 감싸져 전달되므로 적절히 잡아 처리합니다.
  • 의존 작업에는 CompletableFuture를 선호하세요: thenApply, thenCompose 등을 사용하고 Future 객체를 중첩하는 방식을 피합니다.
  • 블로킹 호출을 지연시키세요: 결과가 실제로 필요할 때만 .get()을 호출해 메인 스레드가 가능한 한 오래 자유롭게 유지되도록 합니다.

결론

Future 인터페이스는 현대 Java 동시성의 핵심 요소입니다. “멈추고 기다리는” 로직을 비동기 파이프라인으로 대체해 더 빠르고, 더 신뢰성 있으며, 유지보수가 쉬운 애플리케이션을 만들 수 있게 합니다. Future와 그보다 풍부한 형제인 CompletableFuture를 마스터하는 것은 동시성 코드를 다루는 모든 Java 개발자에게 필수적인 역량입니다.

0 조회
Back to Blog

관련 글

더 보기 »

스토어3

gradle 작업 runQuantumtype: JavaExec { dependsOn prepareLibDir, classes systemProperty 'org.gradle.scan.acceptTerm', 'true' doFirst { setTmpDir buildFileSystem'...