쓰레드라는 키워드는 정말 무수히 뻗어나가는 가지를 지닌 나무 같습니다.
무수히 많은 가지만큼, 아래로 뻗어있는 뿌리도 아주 깊고 넓습니다.
서버 성능 측정 및 튜닝 관련하여 쓰레드풀, 커넥션풀 등의 키워드가 떠오르고
CPU의 쓰레드, OS의 쓰레드까지 하나의 단어가 여러 맥락에서 사용되기도 하고..
결국 한 스푼 한 스푼 떠먹는 수밖에 없는 것 같습니다!
조금씩 조금씩 쌓아가보죠~!
쓰레드 풀(Thread Pool)이란?
쓰레드 풀은 제한된 수 만큼의 쓰레드를 미리 생성해두는 곳을 말합니다.
이를 통해 아래와 같은 장점을 취할 수 있습니다.
- 향후 새로운 처리를 위해 스레드 생성 비용을 매번 발생시키지 않게 된다
- 스레드의 과도한 증폭으로 인한 CPU, 메모리 사용에 따른 성능 저하를 방지한다
- 요청이 많아져도 큐에 많이 쌓일 뿐, 개별 스레드의 성능이 저하되지 않습니다
처리 방식은 매번 새로운 쓰레드를 생성하는 대신,
쓰레드 풀에 있는 작업 큐에 전달되는 작업들을 쓰레드 풀에 있는 쓰레드들이 맡아서 처리하는 식입니다.
이 지점에서 쓰레드를 직접 사용하는 것과 큰 차이가 있습니다.
직접 쓰레드를 다룬다면 쓰레드를 직접 실행시키지만,
쓰레드 풀을 사용한다면 쓰레드 풀의 작업 큐에 수행할 내용을 전달만 할 뿐,
실제 그 작업을 수행하는 주체는 쓰레드 풀입니다.
java.util.concurrent.ThreadPoolExecutor
자바에서 쓰레드 풀을 만든다면 위와 같은 코드로 만들 수 있습니다.
Executors.newFixedThreadPool(), Executors.newCachedThreadPool() 등의 정적 메서드도
내부적으로 ThreadPoolExecutor 생성자를 사용하고 있습니다.
각 매개변수에 대한 설명은 주석으로 대신하겠습니다.
corePoolSize – the number of threads to keep in the pool,
even if they are idle, unless allowCoreThreadTimeOut is set
maximumPoolSize – the maximum number of threads to allow in the pool
keepAliveTime – when the number of threads is greater than the core,
this is the maximum time that excess idle threads will wait for new tasks
before terminating.
unit – the time unit for the keepAliveTime argument
workQueue – the queue to use for holding tasks before they are executed.
This queue will hold only the Runnable tasks submitted by the execute method.
주요 메서드 및 클래스 다이어그램
최상위 Executor 인터페이스에는 void execute(Runnable command) 하나만 정의되어 있습니다.
이를 확장한 ExecutorService 인터페이스는 메서드가 많이 추가되었는데요,
그림에는 일부 주요 메서드만 표기하였습니다.
- void shutdown()
- 작업중인 쓰레드 및 작업큐에 있는 작업이 모두 종료된 이후에 종료합니다.
- List<Runnable> shutdownNow()
- 작업중인 쓰레드를 interrupt하며 종료하고, 작업큐에 대기중이던 작업을 반환합니다.
- boolean awaitTermination(long timeout, TimeUnit unit)
- 전달된 시간만큼만 처리 완료를 대기합니다
- 초과시 강제 종료합니다. 처리 완료 후 종료시에는 true가 반환됩니다.
- Future<T> submit(Callable<T>)
- Callable은 반환값이 있다는 점에서 Runnable과 가장 큰차이가 있습니다.
- 추가로 Callable은 Checked Exception을 던질 수 있습니다. (V call() throws Exception)
- Future<?> submit(Runnable)
- 반환값이 없는 Runnable을 매개변수로 받습니다.
- 응답값 Future에 get() 메서드는 정상 처리 완료시 null을 반환합니다.
ExecutorService를 구현한 추상클래스, AbstractExecutorService가 있는데요,
ThreadPoolExecutor 라는 같은 클래스 명으로 java 패키지, tomcat 패키지 모두에
구현 클래스가 존재합니다.
예외 발생 시 execute vs submit
execute 메서드는 예외 발생 시 해당 스레드는 제거됩니다.
쓰레드 풀은 새로운 쓰레드를 생성합니다.
submit 메서드는 예외 발생시 해당 스레드를 다음 작업을 위해 재사용합니다.
execute 메서드 사용 후 예외 발생 시 쓰레드 재 생성 비용이 발생하기 때문에
submit 메서드의 사용을 권장합니다.
execute, submit은 실행 명령어가 아니다
final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
4,
8,
120L,
TimeUnit.SECONDS,
new SynchronousQueue<>()
);
threadPoolExecutor.execute(() -> System.out.println("Hello ThreadPool"));
threadPoolExecutor.submit(() -> System.out.println("Hello ThreadPool"));
쓰레드 풀을 생성하고 Runnable을 execute, submit 하는 코드입니다.
코드만 읽으면 execute 메서드를 통해 즉시 실행되는 것처럼 보이지만,
execute, submit 모두 즉시 실행이 되는 메서드는 아닙니다.
Executes the given command at some time in the future.
위 내용은 최상위 인터페이스인 Executor에 정의된 하나의 메서드, execute메서드의 주석 첫 줄입니다.
미래 어느 때엔가 command를 실행한다는 내용인데요,
이는 execute, submit 메서드가 쓰레드 풀의 작업 큐에 작업을 담는 메서드이기 때문입니다.
쉽게 생각하면 줄을 서는 것 뿐이죠.
아무도 줄에 없었다면 줄을 서자마자 바로 처리가 될 수 있겠지만,
대기줄이 있었다면 some time in the future에 처리가 될 겁니다.
execute, submit 메서드는 실행시키는 메서드가 아니고 작업 큐에 담는 메서드입니다.
Future와 get() 메서드
A Future represents the result of an asynchronous computation.
java.util.concurrent 패키지에 있는 Future 클래스입니다.
비동기 작업이 수행 완료된 결과가 담길 곳이라고 생각하면 쉽습니다.
주요 메서드를 살펴보자면 다음과 같습니다.
- cancel(boolean)
- submit 또는 execute를 통해 작업 큐에 담았던 작업을 취소하는 메서드입니다.
- 아직 작업 큐에서 대기중이어서 성공적으로 취소를 했다면 true를 반환합니다.
- 이미 완료되었거나 다른 이유로 취소할 수 없을 때 false를 반환합니다.
- 작업이 수행 중이라면, 매개변수로 전달된 boolean이 true일 경우 interrupt 하며 중단시킵니다.
- get()
- 비동기 작업 처리 결과를 가져오는 메서드입니다.
- 중요한 점은 block으로 동작한다는 점입니다.
- (개인적으로 Java의 Future&get이 JavaScript의 Promise&await 와 비슷하게 느껴지는데 정확하지 않습니다)
- Future에 .get()메서드를 호출할 경우, 처리 완료시까지 해당 쓰레드가 block되니 주의해서 사용해야 합니다.
- get(long, TimeUnit)
- get()과 유사하지만, 시간 제한을 두는 점이 차이점입니다.
- 전달한 시간 내에 처리가 완료되지 않을 경우 TimeoutException이 발생합니다.
다음 포스팅에선
두 가지 비동기 처리가 모두 완료된 후에 추가 작업을 비동기로 수행하는
예제 코드를 작성하며 복습해보겠습니다.
학습 출처
'Java & Spring' 카테고리의 다른 글
Spring 외부 설정 파일 import 및 프로퍼티 리팩터링 (2) | 2022.11.17 |
---|---|
🙌 쓰레드풀을 이용해 두 비동기 작업 처리 완료 후 후속 처리하기 (0) | 2022.09.29 |
jitpack, github를 이용한 라이브러리 배포하기 (0) | 2022.08.27 |
📦 DTO는 택배상자 (Bean Validation 검증은 누가 하나?) (3) | 2022.07.25 |
의존이 복잡하게 얽힌 Bean들은 어떻게 생성될까? (4) | 2022.07.05 |