public class dd3 {
private static Thread main;
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
Future<Double> future = executor.submit(dd3::doSomething);
main = Thread.currentThread();
try {
Double result = future.get(1, TimeUnit.SECONDS);
System.out.println(result);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RuntimeException(e);
}
}
public static Double doSomething() throws InterruptedException {
Thread.sleep(500);
Thread.State state = main.getState();
System.out.println(state);
int k = 0;
for (int i = 0; i < 100; i++) {
k += i;
}
return (double) k;
}
}
스레드를 만든 다음에 Callable을 주입하고 Future로 결과를 받는 코드다.
비동기 스레드는 0.5초 잔다음에 100까지 더한다.
메인스레드가 future.get()을 호출했을 때 결과가 도착하면 좋지만, 만약 도착하지 않았다면 결과가 올때까지 메인스레드를 대기시킨다.
메인스레드의 상태를 포착하기 위해 비동기 스레드에서 관찰했을때 역시 TIMED_WAITING상태로 나왔다.

스레드가 대기하는 것이 무엇이 잘못인가?
스레드 풀의 스레드를 점유하고 있기 때문에 더 이상의 요청이 들어올 수 없다.
그럼 스레드 풀을 늘리면 되지 않는가?
아니, 스레드 풀을 늘리는 것은 아주 비용이 많이 든다.
자바의 플랫폼 스레드는 OS의 스레드의 래퍼로 1:1관계다. OS에 의해 직접 스케줄링을 받는 커널 수준 스레드이다. 흔히 스레드를 하나만들면 1MB의 메모리가 사용된다고 한다.
스레드 풀을 만들어 놓는 것은 예약한다는 것이다. 블로킹이 두려워 스레드 풀을 미리 많이 만드는 것은, 아직은 요청이 없어 쓰지도 않는데 OS에서 스레드를 만들어서 메모리 공간을 점유시키고 사용은 하지 않아 다른 기능에서 메모리의 부족으로 성능 저하를 일으킬 수 있다.
스레드가 대기하면 안되는 문제는 이제 알 것이다.
Future에는 계산이 끝났는지 확인하는 메서드도 물론 존재한다. 하지만 이것들만으로는 여러 Future의 결과가 있을 때 간결한 동시 실행 코드를 구현하기 어렵다.
예를 들어서 이런게 필요한 것이다.
1. A메서드에서 1부터 100까지 더한다 동시에 C메서드에서 100부터 200까지 더한다
2. A메서드에서 계산이 끝나면 결과를 B메서드에 전달해 10까지 순서대로 곱한다
3. B의 결과가 나오면 B와 C의 결과를 더한다.
이것은 순서가 있다. Future로 이것을 구현하는 것은 쉽지 않다.
그리하여 Future 인터페이스를 구현하는 CompletableFuture클래스가 나오게되었다.
'Java > 동시성, 병렬성' 카테고리의 다른 글
| Executor의 계층도 그림 (0) | 2026.05.03 |
|---|