이번 글에서는 thread pool과 executor service를 사용해 작업을 처리하는 방법에 대해 알아보도록 하겠습니다.
1. Thread Pool & Executor Service
thread pool은 collections of threads로, 각각의 thread는 주어진 task를 concurrently 하게 수행합니다.
위와 같은 쓰레드 풀은 동시에 가동하는 쓰레드 수에 제한을 둘 때 유용합니다.
새 쓰레드를 생성하는 것은 오버헤드가 따르기 때문에, 매 작업마다 새 쓰레드를 생성하지 않고 쓰레드 풀에 작업을 넘기는 방법으로 수행할 수 있습니다.
Thread Pool을 사용하는 Task 객체는 다음과 같이 작성할 수 있습니다.
public class WorkTask extends Thread {
private long createdTime;
private String taskName;
public WorkTask(String taskName) {
this.createdTime = System.currentTimeMillis();
this.taskName = taskName;
}
@Override
public void run() {
Thread currentThread = Thread.currentThread();
long waitedTime = System.currentTimeMillis() - createdTime;
System.out.println(taskName + " got CPU after waiting for " + waitedTime + "ms with thread" + currentThread.getName());
try {
Thread.sleep(3000);
System.out.println(taskName + " completed. Releasing thread" + currentThread.getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
위의 예제에서는 작업을 수행한다는 의미로 3초간 Thread가 sleep 하도록 정의했습니다.
이제 task 객체를 생성해 executor service에 전달하면, excutor service가 thread pool을 활용해 concurrently 하게 작업을 수행합니다.
1-1) newSingleThreadExecutor
thread pool의 thread 갯수를 1개만 사용하고자 한다면 newSingleThreadExecutor를 사용할 수 있습니다.
사용방법은 다음과 같습니다.
private void startThreadPoolExample() {
ExecutorService executorService = Executors.newSingleThreadExecutor();
WorkTask t1 = new WorkTask("Task-1");
WorkTask t2 = new WorkTask("Task-2");
WorkTask t3 = new WorkTask("Task-3");
WorkTask t4 = new WorkTask("Task-4");
WorkTask t5 = new WorkTask("Task-5");
WorkTask t6 = new WorkTask("Task-6");
// submit
executorService.submit(t1);
executorService.submit(t2);
executorService.submit(t3);
executorService.submit(t4);
executorService.submit(t5);
executorService.submit(t6);
// shutdown
executorService.shutdown();
}
task 객체를 만들어 executorService 객체의 submit 함수로 전달하면, executorService는 1개의 thread만 활용해 작업을 순차적으로 처리합니다.
1-2) newFixedThreadPool
1개 이상의 고정된 갯수의 thread를 사용하고자 한다면, newFixedThreadPool를 사용할 수 있습니다.
private void startThreadPoolExample() {
ExecutorService executorService = Executors.newFixedThreadPool(4);
WorkTask t1 = new WorkTask("Task-1");
WorkTask t2 = new WorkTask("Task-2");
WorkTask t3 = new WorkTask("Task-3");
WorkTask t4 = new WorkTask("Task-4");
WorkTask t5 = new WorkTask("Task-5");
WorkTask t6 = new WorkTask("Task-6");
// submit
executorService.submit(t1);
executorService.submit(t2);
executorService.submit(t3);
executorService.submit(t4);
executorService.submit(t5);
executorService.submit(t6);
// shutdown
executorService.shutdown();
}
위와 같이 4개의 thread를 사용하면 처음 4개의 task는 바로 실행되고, 나머지 2개의 task는 작업을 마친 thread가 존재할때 처리되게 됩니다.
1-3) newCachedThreadPool
thread의 갯수를 사용자가 아닌 JVM 이 할당하도록 하려면, newCachedThreadPool을 사용하면 됩니다.
**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
* Calls to {@code execute} will reuse previously constructed
* threads if available. If no existing thread is available, a new
* thread will be created and added to the pool. Threads that have
* not been used for sixty seconds are terminated and removed from
* the cache. Thus, a pool that remains idle for long enough will
* not consume any resources. Note that pools with similar
* properties but different details (for example, timeout parameters)
* may be created using {@link ThreadPoolExecutor} constructors.
*
* @return the newly created thread pool
*/
private void startThreadPoolExample() {
ExecutorService executorService = Executors.newCachedThreadPool();
WorkTask t1 = new WorkTask("Task-1");
WorkTask t2 = new WorkTask("Task-2");
WorkTask t3 = new WorkTask("Task-3");
WorkTask t4 = new WorkTask("Task-4");
WorkTask t5 = new WorkTask("Task-5");
WorkTask t6 = new WorkTask("Task-6");
// submit
executorService.submit(t1);
executorService.submit(t2);
executorService.submit(t3);
executorService.submit(t4);
executorService.submit(t5);
executorService.submit(t6);
// shutdown
executorService.shutdown();
}
newCachedThreadPool은 처리할 작업의 스레드가 많아지면 그 만큼 스레드를 증가하여 생성합니다. 만약, 쉬는 스레드가 많다면 불필요한 스레드를 종료시킵니다.
newCachedThreadPool는 스레드의 갯수에 제한을 두지 않기 때문에 주의하며 사용해야 합니다.
추천서적
파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음
'Java' 카테고리의 다른 글
[Java] Lambda - Using Lambda (0) | 2020.08.16 |
---|---|
[Java] Lambda - Understanding Lambda (0) | 2020.08.16 |
[Java] Multithreading - Producer & Consumer (0) | 2020.08.16 |
[Java] Multithreading - Thread Synchronization (2) (0) | 2020.08.16 |
[Java] Multithreading - Thread Synchronization (1) (0) | 2020.08.16 |