ExecutorService 관련 공부
- CachedThreadPool : 쓰레드를 캐싱하는 쓰레드풀(사실 여기서 쓰이는 캐싱의 의미는 일정시간동안 쓰레드를 검사한다는 뜻.. 60초 동안 작업이 없으면 Pool에서 제거한다)
- FixedThreadPool : 고정된 개수를 가진 쓰레드풀
- SingleThreadExecutor : 한 개의 쓰레드로 작업을 처리하는 쓰레드풀.. 이라기보단 TaskPool의 개념이 더 적합할 것 같다.
도식은 간단하게 위와같이 나타낼 수 있을 것 같다.
여기서 Customer라는건 Application에서 해당 ExecutorService를 사용하는 클래스 정도로 해석하면 될 것 같고,
해당 클래스에서 ExecutorService에 작업을 submit을 하게 되면, ExecutorService 내부에서 해당 작업을 내부적으로 스케쥴링 하면서 적절하게 일을 처리한다.
이 때, Task를 가진 Queue에서 ThreadPool에 있는 쓰레드들이 각기 본인의 Task를 가지고 작업을 처리하기 때문에,
개발자 입장에서는 해당 쓰레드들의 생명주기를 따로 관리할 필요가 없는 것이다.
ExecutorService에 들어가는 type에는 2가지를 제공하는데, Runnable 과 Callable을 상속하여 구현한 클래스를 받을 수 있도록 되어 있다.
Runnable : return 값이 없는 쓰레드
Callable : return 값이 존재하는 쓰레드
리턴값이 있어야하는 경우엔 Callable을, return 값이 없고 쓰레드 독자적으로 작업을 처리할 수 있는 경우에는 Runnable을 상속하여 워커 쓰레드 클래스를 만들어 사용하자.
테스트 프로젝트 구조
간단하게 작업을 처리하는 Worker(Runnable, Callable을 상속받은 클래스), Job(작업을 의미하는 클래스), CustomerService(executorService를 가진 메인 클래스) 로 구성했다.
package executorLab;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Gompang on 2017. 3. 26..
*/
public class CustomerService {
private ExecutorService executorService;
public CustomerService() {
this.executorService = Executors.newFixedThreadPool(4);
}
public void startWork(){
// Start work with ExecutorService
int threadIdSeq = 0;
while(true){
// Create executorLab.Job for executorLab.Worker
Random random = new Random();
Job job = new Job();
job.setFirst(random.nextInt());
job.setSecond(random.nextInt());
// Set job for worker and ready
Worker worker = new Worker(threadIdSeq, job);
// Start job worker to ExecutorService
executorService.submit(worker);
threadIdSeq++;
// Wait for some time to prevent overload
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]){
CustomerService customerService = new CustomerService();
customerService.startWork();
}
}
package executorLab;
/**
* Created by Gompang on 2017. 3. 26..
*/
public class Job {
private int first;
private int second;
public int getFirst() {
return first;
}
public void setFirst(int first) {
this.first = first;
}
public int getSecond() {
return second;
}
public void setSecond(int second) {
this.second = second;
}
}
package executorLab;
/**
* Created by Gompang on 2017. 3. 26..
* ExecutorService의 TASK를 처리할 executorLab.Worker
*/
public class Worker implements Runnable{
public Worker(int id, Job job) {
this.id = id;
this.job = job;
}
private int id;
private Job job;
public Worker() {
}
public void run() {
// executorLab.Worker doing simple work.
// add first and second.
// finally print value and finish
int sum = this.job.getFirst() + this.job.getSecond();
System.out.println("id : " + id + " = " + sum);
}
}
VisualVM을 이용해서 해당 executorService가 제대로 동작하는지 확인해보면, 생성한 쓰레드 개수만큼 잘 생성되어 실행된다
(위의 경우 fixed로 4개를 설정하여 생성한 service이다. pool-1 Thread-1,2,3,4 순으로 생성된 Pool에서 여러개의 작업이 병렬로 잘 처리된다)
fixedThreadPool만 테스트했지만, CachedThreadPool과 SingleThread는 따로 안해봐도 뻔할 것 같아서 하진 않았다.
주의할점은, cachedThreadPool은 쓰레드수가 폭발적으로 증가할 수 있다는 점이다.
Thread의 제한 없이 무한정 생성하고, 해당 쓰레드의 작업이 60초간 없을 경우 Pool에서 제거하는 방식이기 때문에 작업이 계속적으로 쌓이는 환경에서는
해당 Thread가 소멸되는 것보다, 생성되는 양이 더 많을 것으로 사료된다.
SingleThread는.. 음.. 사실 큰 의미는 없다고 생각하면서도, 싱글 쓰레드의 작업을 처리할때 고려해야 할 race-condition이라던지 하는 부분들을 알아서 처리해주는 만큼, 독자적으로 구현하는 것보다 실 서비스에선 해당 클래스를 사용해봄직 하다고 생각한다.
그리고 fixedThreadPool을 생성할때, 해당 머신의 CPU코어수를 기준으로 생성하면 더 좋은 퍼포먼스를 얻을 수 있다.
쓰레드갯수를 16개로 A에서 생성하고, B 라는 머신에서 생성한다고 했을 때 A는 코어수 1개, B는 코어수 4개라고 가정하면
이는 A 머신에서는 Thread를 생성하고 삭제하는 데 드는 switching 비용과.. 관리 비용이 더 들어갈 수도 있는 것이다.
Java에서 실행하는 머신의 CPU 코어수를 얻어오는 함수 -> 종종 쓰이니 알아두도록 하자
int coreCount = Runtime.getRuntime().availableProcessors();
'Development > Java' 카테고리의 다른 글
[Springboot] JPA(Hibernate) 공부 편 (0) | 2017.04.09 |
---|---|
[Spring] JSON Object 관련 (0) | 2017.03.26 |
[Spring] Annotation 관련 (0) | 2017.03.23 |
[JAVA / 네트워크] 비동기 통신 프로그램 샘플 (3) | 2016.06.12 |
[JAVA / 네트워크] 멀티룸 구조에 대하여(게임&채팅 방 여러개) (44) | 2016.05.22 |