아이템 80. 스레드보다는 실행자, Task, Stream을 애용하라.
- 쓰레드는 작업 단위 / 실행 메커니즘을 모두 개발자가 작성해야 함.
- 자바는 각종 실행 메커니즘을 추상화한 ExeuctorService를 지원함. 개발자는 이것을 사용하는게 좋음.
- ExecutorService에는 작업 단위(Runnable, Callable)을 제출해서 사용하도록 하자. 개발자는 작업 단위(Runnable, Callable)만 선언하면 됨.
- 서버의 사용 환경에 맞게 적절한 ExecutorService를 사용하는 것이 중요
- 소규모 서버 → CachedThreadPool
- 대규모 서버 → FixedThreadPool
자바의 Thread는 작업 단위 + 수행 메커니즘을 모두 표현함.
public static void main(String[] args) {
// Task 정의
Thread thread = new Thread(() -> System.out.println("hello"));
// 실행 메커니즘 정의.
thread.start();
}
자바의 Thread는 작업 단위와 실행 메커니즘을 모두 표현한다. 위의 코드에서 살펴보면 다음과 같이 분리되어서 각각 실행되는 것을 알 수 있다.
- new Thread() : 작업해야 할 Task를 정의함.
- thread.start() : 쓰레드의 실행 메커니즘을 정의함.
예전에는 쓰레드를 직접 사용하면서 작업 단위와 수행 메커니즘을 직접 관리했어야 했다. 이런 부분은 굉장히 곤혹스러울 수 있다. 그러나 자바에서 ExecutorService가 지원되기 시작했는데, ExecutorService는 실행 메커니즘을 담당한다. 이제 개발자는 Task만 선언하고, 실행 메커니즘은 ExecutorService에 맡기면 된다.
자바의 ExeuctorService
ExecutorService는 쓰레드가 수행 해야 할 작업의 실행 메커니즘을 담당하는 녀석이다. 이미 여러 실행 메커니즘을 지원하는 ExecutorService들이 선언되어 있으며, 개발자는 각 ExeuctorService의 특성을 파악하고 어플리케이션에 맞게 가져다 사용하면 된다.
ExecutorService executorService = Executors.newSingleThreadExecutor();
ExecutorService executorService1 = Executors.newCachedThreadPool();
ExecutorService executorService2 = Executors.newFixedThreadPool(10);
예를 들어 cachedThreadPool은 소규모 어플리케이션에 적합하다. CachedThreadPool은 새로운 작업 요청이 들어왔을 때, 처리할 쓰레드가 없으면 새로운 쓰레드를 생성한다. 작업이 밀려들어오고, 처리되지 못한 작업들이 많아서 CPU가 100%를 사용하기 시작하면 작업 요청이 올 때 마다 쓰레드를 생성하면서 점점 처리할 수 없게 된다. 만약에 많은 요청이 오는 서버에서 사용한다면 FixedThreadPool을 사용하는 것이 좋다.
이처럼 이미 ExeuctorService는 쓰레드가 수행해야 할 실행 메커니즘을 이미 잘 구현해두었다. 개발자는 이것을 가져다 쓰기만 하면 된다.
ExeuctorService를 사용하면 Task를 사용하자
쓰레드는 작업 / 실행 메커니즘으로 나누어져 있다. ExecutorService는 실행 메커니즘을 담당하고, 개발자는 작업(Task)만 정의해서 ExecutorService에 제출하면 된다. 여기서 Task는 Runnable / Callable로 나누어진다. 각각은 다음 차이가 있다.
- Runnable : 리턴 타입이 없고, 예외도 없음.
- Callable : 리턴 타입이 있고, 예외도 던질 수 있음.
Callable이라는 작업 단위를 하나 만들어서 ExeuctorService에 제출해서 결과를 받아보는 코드는 아래에서 확인할 수 잇다.
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 작업 단위 정의
Callable<String> stringCallable = () -> {
System.out.println("hello");
return "hello";
};
Future<String> submit = executorService.submit(stringCallable);
try {
String result = submit.get();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
Effective Java : 아이템 84. 프로그램의 동작을 스레드 스케쥴러에 기대지 말라 (0) | 2023.09.05 |
---|---|
Effective Java : 아이템 81. wait, notify보다는 동시성 유틸리티를 애용하라. (0) | 2023.09.04 |
Effective Java : 아이템 79. 과도한 동기화는 피하라. (0) | 2023.09.03 |
Effective Java : 아이템 78. 공유 중인 가변 데이터는 동기화해 사용하라. (0) | 2023.08.31 |
Effective Java : 아이템 77. 예외를 무시하지 말라 (0) | 2023.08.31 |