Thread pool 이란 ? JAVA Thread Pool 스레드풀 사용하기, 개념 설명, 장단점, 예제
Thread pool 이란 ???
스레드를 미리 만들어 풀장에 풀어놓는 (?) 그런 개념으로 생각하면 쉽다.
Thread Pool의 장점
스레드를 생성/수거하는데 드는 비용이 들지 않는다.
스레드가 생성될 때 OS가 메모리 공간을 확보해주고 메모리를 스레드에게 할당해준다.
스레드 풀을 미리 만들어 두기 때문에 처음에 생성하는 비용은 들지만 이전의 스레드를 재사용할 수 있으므로 시스템자원을 줄일 수 있고,
작업을 요청시 이미 스레드가 대기중인 상태이기때문에서 작업을 실행하는데 딜레이가 발생하지 않는다.
thread pool의 단점
thread pool에 thread를 너무 많이 생성해 두었다가 사용하지 않으면 메모리 낭비가 발생한다.
예를 들어 pool에 100개의 thread를 미리 생성해 두었는데 1개의 thread만 사용한다면 어떻게 될까?
99개의 thread는 사용되지 않고 메모리만 차지하고있는 상황이 생길 수 있다.
Thread Pool(스레드 풀)의 동작 원리
우리가 만든 어플리케이션에서 사용자로부터 들어온 요청을 작업큐에 넣고
스레드풀은 작업큐에 들어온 Task일감을 미리 생성해놓은 Tread들에게 일감을 할당한다.
일을 다 처리한 Thread들은 다시 어플리케이션에게 결과값을 리턴한다.
자바에서는 스레드풀을 생성하고 사용할 수 있도록 java.util.concurrent Package에서 ExecutorService 인터페이스와 Executors 클래스를 제공하고 있다.
Executors의 다양한 정적 메서드를 통해 ExecutorService 구현객체를 만들어서 사용할 수 있으며, 그것이 바로 스레드 풀이다.
스레드풀을 사용하는 이유?
1. 프로그램 성능저하를 방지하기 위해
매번 발생되는 작업을 병렬처리하기 위해 스레드를 생성/수거하는데 따른 부담은 프로그램 전체적인 퍼포먼스 저하시킨다.
따라서 스레드풀을 만들어 놓는다.
2. 다수의 사용자 요청을 처리하기 위해
대규모 프로젝트에서 중요하다. 다수의 사용자의 요청을 수용하고, 빠르게 처리하고 대응하기 위해 스레드풀을 사용한다.
Thread Pool(스레드 풀) 생성/종료
1. 스레드 풀 생성
ExecutorService 구현 객체는 Executors 클래스의 다음 두가지 메소드 중 하나를 이용해 간편하게 생성할 수 있다.
생성방밥에 앞서 알아야 할 개념이 있다.
초기 스레드 수 : ExecutorService 객체가 생성될 때 기본적으로 생성되는 스레드 수
코어 스레드 수 : 스레드가 증가한 후 사용되지 않은 스레드를 스레드 풀에서 제거할 때 최소한으로 유지해야할 수
최대 스레드 수 : 스레드풀에서 관리하는 최대 스레드 수
1. newCachedThreadPool()
초기스레드 수, 코어스레드 수 0개 최대 스레드 수는 integer 데이터타입이 가질 수 있는 최대 값(Integer.MAX_VALUE)
스레드 개수보다 작업 개수가 많으면 새로운 스레드를 생성하여 작업을 처리한다.
만약 일 없이 60초동안 아무일을 하지않으면 스레드를 종료시키고 스레드풀에서 제거한다.
2. newFixedThreadPool(int nThreads)
초기 스레드 개수는 0개 ,코어 스레드 수와 최대 스레드 수는 매개변수 nThreads 값으로 지정,
이 스레드 풀은 스레드 개수보다 작업 개수가 많으면 마찬가지로 스레드를 새로 생성하여 작업을 처리한다.
만약 일 없이 놀고 있어도 스레드를 제거하지 않고 내비둔다.
newCachedThreadPool(),newFixedThreadPool() 메서드를 사용하지 않고 직접 스레드 개수들을 설정하고 싶다면
직접 ThreadPoolExecutor 객체를 생성하면 된다.
2. 스레드 풀 종료
스레드 풀에 속한 스레드는 기본적으로 데몬스레드(주 스레드를 서포트하기 위해 만들어진 스레드, 주 스레드 종료시 강제 종료)가 아니기 때문에 main 스레드가 종료되어도 작업을 처리하기 위해 계속 실행 상태로 남아있다. 즉 main() 메서드가 실행이 끝나도 어플리케이션 프로세스는 종료되지 않는다. 어플리케이션 프로세스를 종료하기 위해선 스레드 풀을 강제로 종료시켜 스레드를 해체시켜줘야 한다.
ExecutorService 구현객체에서는 기본적으로 3개 종료 메서드를 제공한다.
excutorService.shutdown();
- 작업큐에 남아있는 작업까지 모두 마무리 후 종료 (오버헤드를 줄이기 위해 일반적으로 많이 사용.)
excutorService.shoutdownNow();
- 작업큐 작업 잔량 상관없이 강제 종료
excutorService.awaitTermination(long timeout, TimeUnit unit);
- 모든 작업 처리를 timeout 시간안에 처리하면 true 리턴 ,처리하지 못하면 작업스레드들을 interrupt()시키고 false리턴
// ExecutorService
private ExecutorService executorService;
public void insert...save(final DataVO datavo) {
/**
* 스레드 풀 생성.
* 스레드를 관리하기 위해 생성
* 스레드를 관리함으로써 매 스레드를 생성하고 소멸시킬 때 오버헤드를 줄일 수 있다.
* 백그라운드 스레드 때문에 발생하는 과부하를 방지한다.
* 스레드 풀 생명주기에 의해 제어한다.
* 스레드풀 생명주기는 스레드 실행 -> shutdown -> 중단 -> 풀과 큐가 비어짐 -> 정리 -> 종료
*/
executorService = Executors.newSingleThreadExecutor();
// single Thread 사용. 단일 쓰레드 사용으로 task가 차례대로 실행되고 스레드가 안전하다.
// 쓰레드를 사용하는 이유 - 동시성 요청에 대한 처리속도 증가.
// Runnable 클래스를 상속받아 run 메서드를 호출한다.
// 호출 시 스레드 풀 (executorService)의 execute 메서드를 호출한다.
Runnable task = new Runnable(){
@Override
public void run() {
for(int i=0; i<dataVO.getList().size(); i++){
DataBean dataBean = new DataBean();
dataBean = dataVO.getList().get(i);
testDAO.insertSave(dataBean);
}
}
};
try {
// thread 실행
executorService.execute(task);
} catch (Exception e) {
e.printStackTrace();
}
// 스레드 작업이 끝난 후 스레드 실행을 종료한다.
// 종료는 꼭 해야함. 하지 않을 경우 메모리 릭 발생 할 수 있음.
// shutdownNow 메서드는 실행중인 스레드에 관계없이 바로 종료
// shutdown 메서드는 스레드 실행이 끝난 후 종료.
executorService.shutdown();
}