티스토리 뷰
스레드 풀을 사용하는 이유
병렬처리 작업이 많아지면 스레드의 개수가 증가할 수 있다. 스레드가 매우 많아지게 되면 스레드를 생성하는 작업과 스케쥴링 작업으로 인해 CPU와 메모리 사용량이 늘어나게 되며 결국 애플리케이션 성능이 급격히 저하될 수 있다.
만약 자바로 웹서버를 구성했고 하나의 요청마다 스레드를 생성하는 상황이라고 가정을 했을 때, 만약 수백 수천개의 요청이 들어온다면 스레드도 수백 수천개가 만들어질 것이다. 이렇게되면 요청이 많이 들어올 때 스레드수가많아지므로 애플리케이션 성능이 저하될 수 있다.
따라서 스레드 풀
을 통해 작업 처리에 사용되는 스레드를 제한된 개수만큼만 미리 생성할 수 있다. 최대 스레드의 개수를 정해놓고 작업큐에 들어오는 작업들을 스레드가 하나씩 맡아서 처리하게 된다. 즉 작업큐에는 수백,수천개의 작업이 들어올 수 있지만 스레드는 정해진 수 만큼만 실행되게 되어 애플리케이션의 성능을 유지할 수 있다.
스레드풀을 생성하고 사용할 수 있도록 자바에서 java.util.concurrent
패키지에서 인터페이스와 클래스를 제공하고 있다. Executors
의 정적 메소드를 이용해 ExecutorService
구현 객체를 생성하며 이 객체가 스레드 풀이 된다.
ExecutorService
는 다음과 같이 동작한다.
이 안에는 작업큐
라고 하는 작업을 저장하는 공간이 있다. 그리고 이 작업큐에서 작업을 가져와 실행할 스레드
가 존재하고 있다. 이 때 스레드의 최대 개수는 제한되어 있다.
스레드 풀에서 초기 스레드수
는 스레드 풀을 만들때 기본 스레드 수를 의미하며 코어 스레드 수
는 많은 수의 스레드가 있을경우 사용되지 않는 스레드를 풀에서 제거하는데 최소한 유지되어야 하는 스레드를 의미한다. 최대 스레드 수
는 스레드가 최대로 늘어날 수 있는 수를 의미한다.
스레드 풀 생성
스레드풀은 다음 메소드로 생성할 수 있다.
newCachedThreadPool()
or newFixedThreadPool(int n)
newCachedThreadPool
은 int의 최대값만큼 스레드가 추가되나 운영체제의 상황에 따라 달라질 수 있다. 스레드는 60초동안 아무작업하지 않을경우 제거된다.
ExecutorService executorService = Executors.newCachedThreadPool()
로 생성한다
newFixedThreadPool(int n)
은 코어 스레드 수와 최대 스레드 개수가 n만큼 가지게 된다. 스레드의 수를 Fix해놓았으므로 스레드가 작업을 처리하지 않고 놀고있더라도 스레드가 제거되지 않는다.
ExecutorService executorService = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors() );
이 코드는 CPU가 사용가능 한 코어의 개수만큼 스레드를 가지는 스레드 풀을 생성하는 코드이다.
이 외에도 ThreadPoolExecutor
을 이용하여 스레드 풀을 집적 생성할 수 있다.
이렇게 생성하면 스레드의 수를 좀더 집적 관리하여 생성할 수 있다.
ExecutorService threadPool = new ThreadPoolExecutor(
3, //코어 스레드 개수
50, //최대 스레드 개수
120L, //놀고 있는 시간
TimeUnit.SECONDS, //놀고있는 시간단위
new SynchronousQueue<Runnable>() //작업큐
)
스레드풀은 기본적으로 데몬 스레드가 아니다. 따라서 main스레드가 종료되더라도 스레드풀의 스레드는 작업을 처리하기 위해 계속 실행되기에 애플리케이션이 종료되지 않을 수 있다. 따라서 스레드풀을 종료해서 모든 스레드를 종료시켜야 한다.
스레드풀을 종료할 때는 현재 처리중인 작업과 작업큐의 작업을 모두 종료하는 shutdown()
함수와 현재 작업 처리중인 스레드를 interrupt하여 작업을 중지하고 아직 작업큐에 있는 작업의 목록을 리턴해주는 shutdownNow()
함수가 있다.
스레드 풀에 작업 생성
스레드 풀의 작업큐에 들어갈 작업을 생성할 때는 Runnable
또는 Callable
객체로 표현한다. 작업에 리턴값이 없다면 Runnable
로 리턴값이 있다면 Callable
로 생성하면 된다. 결국 스레드풀이 하는일은 작업큐에서 이 작업들을 가져와 스레드로 하여금 run()
메소드 혹은 call()
메소드를 호출하는 일이다.
작업처리를 요청하는 것은 스레드풀의 작업큐에 생성한 작업을 넣는 행위이다.execute(Runnable task)
메소드 혹은 submit(Runnable task)
메소드를 통해 작업큐에 넣을 수 있다.
위 순서를 코드로 작성하면 이렇게 표현할 수 있다.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Example {
public static void main (String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i=0; i<5; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println("작업 처리");
}
};
executorService.execute(runnable);
}
executorService.shutdown();
}
}
위 코드는 2개의 스레드를 가지는 스레드 풀을 생성하고 총 5개의 스레드를 생성하여 스레드풀을 통해 실행시키는 코드이다.
결과는 다음과 같다.
pool-1-thread-2
작업 처리
pool-1-thread-2
작업 처리
pool-1-thread-2
작업 처리
pool-1-thread-2
작업 처리
pool-1-thread-1
작업 처리
이처럼 총 2개의 스레드를 통해 5개의 작업을 처리한 것을 볼 수 있다.
이외에도 스레드풀은 작업처리 결과를 관리하고 저장하는 작업을 지원한다.
'Java' 카테고리의 다른 글
[JAVA] 자바 Optional 사용 방법 (0) | 2022.03.12 |
---|---|
[JAVA] 자바 스트림(Stream) (0) | 2022.03.12 |
[JAVA] 자바 스레드 우선순위 및 동기화 (0) | 2022.03.04 |
[JAVA] 자바 스레드 생성 및 실행 (0) | 2022.03.04 |
[JAVA] 자바 객체를 복제하는 방법 (0) | 2022.03.03 |
- Total
- Today
- Yesterday
- node.js
- 백준
- 그래프
- nestjs
- nodeJS
- java
- Computer Architecture
- 스레드
- nest.js
- 재귀
- ReactNative
- 자바
- 동적계획법
- typeORM
- 벨만포드
- 시뮬레이션
- 그리디
- 예외처리
- 컴퓨터 구조
- boj
- 세그먼트 트리
- BFS
- 알고리즘
- 구현
- 중앙대학교
- 컴퓨터 통신
- dfs
- 투포인터
- 자바스크립트
- 백트래킹
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |