최신 비동기 기술 (Executor, Virtual Threads)
자바에서는 단순히 Thread 인스턴스를 직접 생성하여 관리하는 단계를 넘어, 더욱 효율적이고 모던한 멀티 쓰레딩 환경을 제공하는 방법들이 발전해왔습니다. 특히 ExecutorService와 자바 21에 정식 도입된 Virtual Threads가 대표적입니다.
1. Executor 프레임워크 기반 쓰레드 풀
쓰레드를 생성하고 소멸시키는 작업은 시스템 리소스를 크게 소모합니다. 애플리케이션에 요청이 들어올 때마다 쓰레드를 무한정 만들면 시스템이 다운될 수 있습니다.
이를 방지하기 위해 미리 쓰레드를 여러 개 생성하여 풀(Pool) 에 담아두고, 작업이 발생하면 쓰레드를 꺼내 쓰고 완료되면 반환하는 쓰레드 풀(Thread Pool) 개념이 도입되었습니다. 이를 쉽게 구현하게 해주는 것이 java.util.concurrent 패키지의 ExecutorService 입니다.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 크기가 5인 고정된 쓰레드 풀 생성
ExecutorService executor = Executors.newFixedThreadPool(5);
for(int i = 0; i < 10; i++) {
final int taskNum = i;
executor.execute(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("작업 " + taskNum + " 처리: " + threadName);
});
}
// 작업이 모두 처리되면 쓰레드 풀 종료
executor.shutdown();
}
}
2. CompletableFuture (자바 8)
비동기 작업의 결과를 조합하거나, 예외를 처리하는 등 콜백 방식의 단점을 해결하고 체이닝을 통해 비동기 프로그래밍을 우아하게 작성할 수 있게 해주는 기능입니다. 자바스크립트의 Promise와 매우 유사합니다.
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "비동기 작업 결과";
}).thenApply(result -> {
return result + " -> 가공됨";
});
System.out.println(future.get()); // 결과 출력: 비동기 작업 결과 -> 가공됨
}
}
3. 가상 쓰레드 (Virtual Threads - 자바 21+)
가상 쓰레드(Virtual Thread)는 자바 플랫폼에 새롭게 추가된 가벼운 쓰레드입니다. 전통적인 커널(OS) 쓰레드와 1:1로 매핑되는 자바 쓰레드(플랫폼 쓰레드)는 무거워서 수만 개씩 생성하기 어려웠지만, 가상 쓰레드는 메모리와 컨텍스트 스위칭 비용이 매우 작아서 수백만 개를 동시에 생성해도 문제없습니다.
가상 쓰레드 사용 방법
import java.util.concurrent.Executors;
public class VirtualThreadExample {
public static void main(String[] args) throws Exception {
// 가상 쓰레드 생성 방법 1
Thread.ofVirtual().name("my-virtual-thread").start(() -> {
System.out.println("가상 쓰레드 실행 중");
}).join();
// 가상 쓰레드 생성 방법 2 (가상 쓰레드 풀을 제공하는 Executor)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for(int i = 0; i < 100000; i++) { // 10만 개의 쓰레드 생성에도 무리 없음
executor.submit(() -> {
try {
Thread.sleep(1000); // 블로킹이 발생해도 다른 작업을 수행하도록 스위칭됨
System.out.println("작업 완료: " + Thread.currentThread().getName());
} catch (InterruptedException e) {}
});
}
} // try-with-resources 종료 시 자동으로 shutdown됨
}
}
가상 쓰레드는 I/O 바인딩 작업(네트워크 통신, DB 조회)과 같이 시간이 오래 걸리며 대기(Block) 상태에 많이 놓이는 현대식 백엔드 애플리케이션에서 매우 큰 효율 향상을 가져옵니다.