이번엔 Thread를 좀 더 유연하게 활용할수 있는 Thread Pool에 대해서 알아볼 예정이다.
Thread Pool 이란?
Thread Pool은 미리 대기시켜놓은 Thread를 가지고 큐 안에 있는 작업들을 실행하는것이다.
이렇게만 말한다면 큐를 사용해서 작업을 수행한다는것을 제외하고는 일반적인 Thread와 별반 다를것이 없어 보인다.
일반 Thread는 우리가 사용할 Thread를 일일히 만들고 Thread에게 작업을 일일히 나눠주었다면
Thread Pool은 우리가 사용할 Thread의 개수를 미리 설정 해두고 해당 개수만큼 Thread를 생성한뒤 큐안에 있는
작업들을 우리가 만들어 놓은 Thread를 활용하여 실행을 하는것이다.
따라서 Thread Pool 같은 경우는 다수의 Thread를 사용해야 할만큼 다수의 요청을 처리해야할때 주로 사용하게 된다.
(그 이유는 Thread의 생성과 수거를 알아서 잘 해주기 때문이다.)
그렇다면 이제 Thread Pool을 사용하는 방법에 대해서 알아보자.
Thread Pool 사용 방법
자바에서는 java.util.concurrent.ExecutorService 를 통해서 Thread Pool을 제공해준다.
이를 사용하기 위해서 해당 클래스로 객체를 생성해야 한다.
객체를 생성하고 나서는 우리가 해당 ExecutorService로 Thread Pool에 대한 설정을 할수가 있는데
우리가 설정할수 있는것은 아래와 같다.
- newSingleThreadExecutor : 1개의 스레드를 사용하는 스레드풀
- newCachedThreadPool : 개수의 제한이 없는 스레드풀
- newFixedThreadPool : 매개변수에 개수를 지정하여 사용하는 스레드풀
- newScheduledThreadPool : 특정 시간 등을 지정하여 사용할 수 있는 스레드풀
소스를 통해서 한번 살펴보자.
ExecutorService exs = Executors.newFixedThreadPool(500);
CountDownLatch countDownLatch = new CountDownLatch(1667);
나같은 경우는 아래처럼 소스를 작성하였는데 나는 newFixedThreadPool()을 사용해서 500개의 Thread로 Thread 개수를 지정하였다.
그리고 CountDownLatch 인스턴스를 생성하였는데 생성한 이유는 메인스레드가 생성한 Thread의 모든 작업이
끝날때까지 기다리기 위해서 CountDownLatch의 인스턴스를 생성하였다.
그리고 Thread를 실행하는 소스는 아래와 같이 작성 하였다.
for(int i = 0; i<1667;i++) {
exs.submit(new ThreadPool("tpn"+i,exs,countDownLatch));
}
1667번의 ThreadPool 클래스의 작업을 수행하게 하였다.
ThreadPool Class
public class ThreadPool implements Runnable{
private String threadName;
private ThreadPoolExecutor te;
private CountDownLatch cdl;
public ThreadPool(String name,ExecutorService exs,CountDownLatch cdl) {
super();
this.threadName = name;
te = (ThreadPoolExecutor) exs;
this.cdl = cdl;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(te.getPoolSize());
System.out.println("API call");
try {
Thread.sleep(6000);
System.out.println("API call success");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
cdl.countDown();
System.out.println();
}
}
}
그래서 위 소스는 500개의 Thread로 1667번의 ThreadPool 의 작업을 실행하는것이다.
ThreadPool의 인스턴스를 생성할때 ExecutorService와 CountDownLatch를 인자로 받았는데
받은 ExecutorService를 받은 이유는 현재 작동하고 있는 Thread의 개수를 파악하기 위해서 인자로 받았고
CountDownLatch를 받은이유는 CountDownLatch의 수를 CountDown해서 1667개의 CountDown을 작업을
완료 했을때 줄이기 위해서 이다.
따라서 1667개가 모두 없어진다면 그제서야 모든 Thread의 작업이 끝난것이니 그때를 확인하기 위해서이다.
무튼 이렇게 Thread를 생성하고 작업도 실행 시켰으니 이제 생성한 Thread Pool을 종료 시켜줘야 한다.
Thread 종료 방법
스레드 풀에 속한 스레드는 기본적으로 데몬스레드(주 스레드를 서포트하기 위해 만들어진 스레드, 주 스레드 종료시 강제 종료)가 아니기 때문에 main 스레드가 종료되어도 작업을 처리하기 위해 계속 실행 상태로 남아있다. 즉 main() 메서드가 실행이 끝나도 어플리케이션 프로세스는 종료되지 않는다. 어플리케이션 프로세스를 종료하기 위해선 스레드 풀을 강제로 종료시켜 스레드를 해체시켜줘야 한다.
ExecutorService 구현객체에서는 기본적으로 3개 종료 메서드를 제공한다.
excutorService.shutdown();
- 작업큐에 남아있는 작업까지 모두 마무리 후 종료 (오버헤드를 줄이기 위해 일반적으로 많이 사용.)
excutorService.shoutdownNow();
- 작업큐 작업 잔량 상관없이 강제 종료
excutorService.awaitTermination(long timeout, TimeUnit unit);
- 모든 작업 처리를 timeout 시간안에 처리하면 true 리턴 ,처리하지 못하면 작업스레드들을 interrupt()시키고 false리턴
나는 작업 큐에 남아 있는 작업을 완료 해야하기 떄문에 아래와 같이 코드를 작성 하였다.
exs.shutdown();
countDownLatch.await();
System.out.println("Main Thread End");
이렇게 하게 되면 작업 큐에 남은 작업들은 모두 완료ㄹ르 하고 그리고
CountDownLatch.await()를 함으로써 메인 Thread는 남은 작업이 끝날때까지 대기하게 된다.
그리고 모두 끝나게 되면 그제서야 "MainThread End" 를 출력하게 된다.
내가 Thread의 작업을 6초동안 Thread.sleep() 시켰던 이유는 내가 앞으로 호출할 API의 결과를 받기까지 6초가
걸리기 때문이다.
내가 만약 1개의 Thread로 6초나 걸리는 API를 1667번 호출 하게 되면 10002초 = 166분 = 약 3시간 정도를 기다려야 한다.
하지만 위 소스 처럼 500개의 Thread로 작업을 하게 되면 24초가 소요된다.
Main Thread Start
Sun Nov 21 13:03:36 KST 2021
...
Main Thread End
Sun Nov 21 13:04:00 KST 2021
이처럼 많은 처리량을 요구한다면 ThreadPool을 사용해서 처리속도를 줄일수가 있다.
'코딩일기 > 날씨앱 만들기 프로젝트' 카테고리의 다른 글
[Spring Boot] Scheduler 설명 및 사용법 (0) | 2021.12.13 |
---|---|
[Java] CompletionHandler를 이용한 Thread 콜백 구현 (0) | 2021.12.02 |
[Java] Thread 설명 및 사용법 (0) | 2021.11.16 |
[Spring Boot] MyBatis를 통한 MySQL 연동 (maven) (0) | 2021.11.12 |
[Spring Boot] properties 를 통해서 키 값 숨기기 (0) | 2021.10.24 |