[Java] Multithreading - Thread Pool & Executor Service

반응형

이번 글에서는 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 프로그래밍 정복

COUPANG

www.coupang.com

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음


반응형

댓글

Designed by JB FACTORY