[Java Library] Executor Framework
- Language & Solution/Java
- 2018. 9. 25. 03:23
Executors는 JDK에서 제공하는 framework로서 Java application에서 실행되는 task를 간단하게 비동기로 처리할 수 있게 해주는 thread-pool과 API를 제공합니다. Java application 상에서 thread를 한 두개를 만들어 돌리는 것은 그렇게 어렵지 않습니다. 하지만 그 숫자가 20, 30 혹은 그보다 많아질 경우에는 이 많은 thread를 어떻게 관리할 것인지 문제가 되기 시작합니다. 이 문제를 Executors framework을 통해 간단히 처리할 수 있습니다.
Executors framework가 하는 일은 크게 3가지 입니다.
1. Thread 생성 : thread를 생성하거나 thread pool을 만드는 method를 제공합니다.
2. Thread 관리 : thread의 생명주기를 관리합니다. thread pool이 활성화되어 있는지 죽은 상태인지에 대해 고려하지 않아도 되게끔 thread를 관리합니다.
3. Task 제출 및 실행 : Runnable 혹은 Callable method를 제출하고 그것을 원하는 때에 실행할 수 있게 해줍니다.
| ExecutorService instance 만들기
ExecutorService의 instance를 만드는 가장 쉬운 방법은 Executors class에서 제공하는 factory method 중 하나를 사용하는 겁니다. 예를 들어 10개의 thread가 있는 thread-pool을 만들고 싶다면 다음 코드와 같이 newFixedThreadPool을 사용하면 됩니다.
ExecutorService executor = Executors.newFixedThreadPool(10);
위 예시말고 다른 요구사항에 맞는 executor instance를 만들고 싶다면 오라클 공식 문서를 참조하셔서 구현하시면 됩니다.
이외에도 java.util.concurrent 패키지를 이용하여 직접 new 키워드를 써서 구현하는 방법이 있습니다.
| ExecutorService에게 task 할당하기
ExecutorService는 Runnable과 Callable task들을 실행할 수 있습니다. Runnable과 Callable은 모두 Functional Interface이고, 이 Interface에 ExecutorService에서 실행할 함수를 할당하는 방식이죠. 아래 코드는 그에 관한 예시입니다.
Task들은 ExecutorService의 method들을 이용하여 ExecutorService Instance에 할당할 수 있거나 실행될 수 있습니다.
다음은 executorService에서 제공하는 대표적인 method들입니다.
execute()는 Runnable task를 ExecutorService에 할당하여 미래 어떤 특정 시점에 실행되도록 합니다. 이 때, 어떠한 성공적으로 실행됬는지에 대한 체크는 하지 않습니다.
executorService.execute(runnableTask);
| ExecutorService 예제
다음은 Executor framework를 사용한 예제입니다.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorMain { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); Runnable runnable = () -> { System.out.println("inside : " + Thread.currentThread().getName()); }; executorService.submit(runnable); } }
위 예제에서는 단일 thread를 생성하고 관리하기 위해 newSingleThreadExecutor를 사용하였습니다. 만일 task가 실행되기 위해 제출되면, 다른 task를 실행하느라 바쁜 상태에 있는 thread를 제외하고 thread pool에 있는 유휴 thread가 할당되기를 기다립니다. 이 때, 제출된 task는 queue에 대기하는 상태로 됩니다.
프로그램을 실행하면 프로그램이 종료되지 않는다는 것을 알 수 있습니다. 왜냐하면 명시적으로 application을 종료하지 않는 이상 ExecutorService는 다른 새로운 task가 들어올 때까지 대기하고 있기 때문입니다.
| ExecutorService 종료하기
ExecutorService는 executor service를 종료하기 위해 두 가지 method를 제공합니다.
1. shutdown() : executor service에서 이 method가 실행될 때, 새로운 task가 할당되는 것을 막고 이미 전에 제출된 task가 실행되는 것을 기다린 후 executor instance를 종료합니다.
2. shutdownNow() : exeutor service를 곧바로 종료합니다.
아래는 executor service를 종료하는 코드를 추가한 예제입니다.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class ExecutorMain { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); Runnable task1 = () -> { System.out.println("Executing Task1 inside : " + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } }; executorService.submit(task1); executorService.shutdown(); } }
| 다중 threads 및 tasks를 ExecutorService로 구현한 예제
다음은 ExecutorService를 통해 다중 threads를 생성하고 관리하는 예제입니다.
// output Executing Task2 inside : pool-1-thread-2 Executing Task1 inside : pool-1-thread-1 Executing Task3 inside : pool-1-thread-1
위 예제에서는 newFixedThreadPool를 사용하여 2개의 고정된 thread들을 관리하는 thread pool을 생성하였습니다. 고정된 thread pool에서는 executor service가 생성한 pool이 언제나 특정한 thread들이 실행된다는 것을 보장하죠. thread pool에서는 pool안의 thread가 죽더라도 그 자리를 새로운 thread가 즉시 대체하게 되어있습니다.
새로운 task가 제출되면 executor service는 현재 할당이 가능한 thread를 pool에서 뽑습니다. 그리고 그 task를 뽑은 thread에 할당하죠. 만약 thread들이 이전에 할당된 task들을 처리해야해서 할당가능한 thread가 존재하지 않으면, 그 이후에 제출된 task들은 queue에서 대기하게 됩니다.
| Executor Service와 Thread Pool 구조
Executor Service는 Thread Pool과 Blocking Queue로 구성되어 있습니다. 제출된 task들은 blocking queue에 들어가게 되고 위에서 언급한 메커니즘에 의해서 유휴 thread에 할당됩니다. ( thread 남음 => 할당 받음, thread가 안 남음 => 대기함 )
thread를 생성하는 것은 비용이 큰 작업이므로 이 작업을 최소화 하기 위해 Executor service에서는 미리 thread pool안에 thread를 생성해 놓고 관리합니다. 한 번 생성해 놓고 계속 재사용하는 것이죠.
| ScheduledExecutorService 예제
다음은 executor service에서 scheduling을 통해 thread를 실행하는 예제입니다. newScheduledThreadPool method를 통해서 1개의 thread를 관리하는 pool을 만들고 5초 뒤 task를 실행합니다.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ExecutorMain { public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); Runnable task = () -> { System.out.println("Executing Task At " + System.nanoTime()); }; System.out.println("Submitting task at " + System.nanoTime() + " to be executed after 5 seconds."); scheduledExecutorService.schedule(task, 5, TimeUnit.SECONDS); scheduledExecutorService.shutdown(); } }
//output Submitting task at 53978948987383 to be executed after 5 seconds. Executing Task At 53983950911138
다음은 주기적으로 task를 실행하는 예제입니다.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ExecutorMain { public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); Runnable task = () -> { System.out.println("Executing at " + System.nanoTime()); }; System.out.println("scheduling task to be executed every 2 seconds with an initial delay of 0 seconds"); scheduledExecutorService.scheduleAtFixedRate(task, 0, 2, TimeUnit.SECONDS); } }
scheduling task to be executed every 2 seconds with an initial delay of 0 seconds Executing at 54254984054269 Executing at 54256983981223 Executing at 54258984504146 Executing at 54260983313575 Executing at 54262986380456 Executing at 54264984489469 Executing at 54266983327570
| 마치며
Executor Framework는 Java application에서 Thread를 생성하고 관리하기 쉽게 다양한 method와 interface를 제공합니다. 당연한 것이지만 executors와 thread pool가 제공하는 모든 기능을 다 다룬 것은 아닙니다. 그 외의 Executor에 관한 내용은 오라클 문서를 참조하셔서 project 상황에 맞게 적용하시면 될 것 같습니다.
출처 : https://www.callicoder.com/java-executor-service-and-thread-pool-tutorial/
https://www.baeldung.com/java-executor-service-tutorial
'Language & Solution > Java ' 카테고리의 다른 글
[Java] 새로운 게 무조건 더 좋은 방법은 아니다: Collections.nCopies()와 Stream에 관하여 (0) | 2019.03.08 |
---|---|
[Java] Java Optional (자바 옵셔널) 정리, 예제모음 (0) | 2018.11.21 |
[Java] Java 클래스 로딩 과정(Java Class Loading Process) (0) | 2018.11.12 |
[Java] JDK 소스코드 붙이기 (0) | 2018.06.24 |
[Java] 서블릿(servlet)과 서블릿 컨테이너(servlet container) (0) | 2017.05.15 |
이 글을 공유하기