개요
Java에서 스레드를 사용하는 방법에 대해 이미 알고 있을 것입니다. 이전 글에서 `Thread` 클래스를 사용하여 작업을 실행하는 기본적인 코드를 살펴보았습니다. 다음은 그 예시입니다:
public static void main(String[] args) throws Exception {
Runnable task = () -> System.out.println("작업 실행");
Thread thread = new Thread(task);
thread.start();
}
이 코드는 스레드를 시작하는 데 필요한 기본적인 구조를 보여줍니다. 그러나 이러한 코드를 반복적으로 작성하는 것은 비효율적입니다. 이를 개선하기 위해 Java는 `Executor` 인터페이스를 제공합니다. 이 인터페이스를 사용하면 다음과 같이 코드를 더 간결하게 만들 수 있습니다:
public static void main(String[] args) throws Exception {
Runnable task = () -> System.out.println("작업 실행");
Executor executor = (runnable) -> {
new Thread(runnable).start();
};
executor.execute(task);
}
`Executor` 인터페이스는 `Runnable` 태스크를 실행할 수 있는 메커니즘을 제공합니다. 이 인터페이스의 하위 인터페이스인 `ExecutorService`는 추가적인 기능을 제공하며, 스레드 풀 관리와 같은 고급 기능을 포함합니다. `ExecutorService` 인터페이스는 Javadoc에서 자세히 설명되어 있으며, `java.util.concurrent.Executors` 팩토리를 통해 다양한 구현체를 생성할 수 있습니다.
ExecutorService
스레드를 직접 생성하는 대신, `ExecutorService`를 사용하면 작업을 제출하고 관리하는 것이 훨씬 쉽습니다. 예를 들어, 크기가 2인 고정 스레드 풀을 생성하고 작업을 제출할 수 있습니다:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<?> future1 = executorService.submit(() -> {
System.out.println("작업 1 실행: " + Thread.currentThread().getName());
return null;
});
Future<?> future2 = executorService.submit(() -> {
System.out.println("작업 2 실행: " + Thread.currentThread().getName());
return null;
});
executorService.shutdown();
}
위 코드에서 `ExecutorService`를 사용하여 크기가 2인 고정 스레드 풀을 생성하고, 두 개의 작업을 제출합니다. 각 작업은 현재 스레드의 이름을 출력한 후 종료됩니다. 마지막으로, `executorService.shutdown()`을 호출하여 스레드 풀을 종료합니다. 이렇게 하면 프로그램이 정상적으로 종료됩니다.
또한, `Executors` 팩토리는 다양한 유형의 스레드 풀을 생성하는 다른 메소드도 제공합니다. 예를 들어, 단일 스레드로 구성된 풀(`Executors.newSingleThreadExecutor()`)이나, 필요에 따라 스레드를 동적으로 생성하고 1분 동안 아이들 상태인 스레드를 제거하는 캐싱 풀(`Executors.newCachedThreadPool()`)을 생성할 수 있습니다.