Executor 인터페이스가 필요한 이유는 무엇입니까?
Java 5 이전에는 애플리케이션에서 모든 자체 코드 스레드 관리를 작성해야 했습니다. 또한,새 스레드개체는 리소스를 많이 사용하는 작업이며 모든 경량 작업에 대해 새 스레드를 만드는 것은 이치에 맞지 않습니다. 그리고 이 문제는 멀티스레드 애플리케이션의 모든 개발자에게 친숙하기 때문에 이 기능을 Executor 프레임워크로 Java에 도입하기로 결정했습니다.
큰 아이디어는 무엇입니까? 간단합니다. 새 작업마다 새 스레드를 만드는 대신 스레드를 일종의 "저장소"에 보관하고 새 작업이 도착하면 새 작업을 만드는 대신 기존 스레드를 검색합니다.
이 프레임워크의 주요 인터페이스는 Executor , ExecutorService 및 ScheduledExecutorService 이며 각각 이전 인터페이스의 기능을 확장합니다.
Executor 인터페이스는 기본 인터페이스입니다. Runnable 개체 에 의해 구현되는 단일 무효 실행(Runnable 명령) 메서드를 선언합니다 .
ExecutorService 인터페이스 가 더 흥미롭습니다. 작업 완료를 관리하는 방법과 일종의 결과를 반환하는 방법이 있습니다. 방법을 자세히 살펴 보겠습니다.
방법 | 설명 |
---|---|
무효 종료(); | 이 메서드를 호출하면 ExecutorService 가 중지됩니다 . 처리를 위해 이미 제출된 모든 작업은 완료되지만 새 작업은 수락되지 않습니다. |
List<Runnable> shutdownNow(); |
이 메서드를 호출하면 ExecutorService 가 중지됩니다 . 처리를 위해 이미 제출된 모든 작업에 대해 Thread.interrupt 가 호출됩니다. 이 메서드는 대기 중인 작업 목록을 반환합니다. 메서드는 메서드가 호출될 때 "진행 중"인 모든 작업이 완료될 때까지 기다리지 않습니다. 경고: 이 메서드를 호출하면 리소스가 누출될 수 있습니다. |
부울 isShutdown(); | ExecutorService 가 중지되었는지 확인합니다 . |
부울 isTerminated(); | ExecutorService 종료 후 모든 작업이 완료되면 true를 반환합니다 . shutdown() 또는 shutdownNow()가 호출될 때까지 항상 false를 반환합니다 . |
부울 awaitTermination(long timeout, TimeUnit unit)은 InterruptedException을 발생시킵니다. |
shutdown() 메서드가 호출 된 후 이 메서드는 다음 조건 중 하나가 참일 때까지 호출된 스레드를 차단합니다.
모든 작업이 완료되면 true를 반환 하고 종료 전에 제한 시간이 경과하면 false를 반환합니다. |
<T> Future<T> 제출(Callable<T> 작업); |
Callable 작업을 ExecutorService 에 추가 하고 Future 인터페이스를 구현하는 개체를 반환합니다 . <T> 는 전달된 작업의 결과 유형입니다. |
<T> Future<T> 제출(실행 가능한 작업, T 결과); |
Runnable 작업을 ExecutorService 에 추가 하고 Future 인터페이스를 구현하는 개체를 반환합니다 . T 결과 매개변수는 결과에 대한 get() 메서드 호출에 의해 반환되는 것입니다.미래 객체. |
Future<?> 제출(실행 가능한 작업); |
Runnable 작업을 ExecutorService 에 추가 하고 Future 인터페이스를 구현하는 개체를 반환합니다 . 결과 Future 객체에서 get() 메서드를 호출하면 null을 얻습니다. |
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> 작업) throws InterruptedException; |
Callable 작업 목록을 ExecutorService 에 전달합니다 . 작업 결과를 얻을 수 있는 Future 목록을 반환합니다. 제출된 모든 작업이 완료되면 이 목록이 반환됩니다. 메서드가 실행되는 동안 작업 컬렉션이 수정되면 이 메서드의 결과는 정의되지 않습니다 . |
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; |
Callable 작업 목록을 ExecutorService 에 전달합니다 . 작업 결과를 얻을 수 있는 Future 목록을 반환합니다. 이 목록은 전달된 모든 작업이 완료되거나 메서드에 전달된 제한 시간이 경과한 후 중 먼저 도래하는 시점에 반환됩니다. 제한 시간이 경과하면 완료되지 않은 작업이 취소됩니다. 참고: 취소된 작업이 실행을 중지하지 않을 수 있습니다(예제에서 이 부작용을 볼 수 있음). 메서드가 실행되는 동안 작업 컬렉션이 수정되면 이 메서드의 결과는 정의되지 않습니다 . |
<T> T invokeAny(Collection<? extends Callable<T>> tasks)는 InterruptedException, ExecutionException을 발생시킵니다. |
Callable 작업 목록을 ExecutorService 에 전달합니다 . 예외(있는 경우)를 throw하지 않고 완료된 작업(있는 경우) 중 하나의 결과를 반환합니다. 메서드가 실행되는 동안 작업 컬렉션이 수정되면 이 메서드의 결과는 정의되지 않습니다 . |
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; |
Callable 작업 목록을 ExecutorService 에 전달합니다 . 메서드에 전달된 제한 시간이 경과하기 전에 예외를 throw하지 않고 완료된 작업(있는 경우) 중 하나의 결과를 반환합니다. 메서드가 실행되는 동안 작업 컬렉션이 수정되면 이 메서드의 결과는 정의되지 않습니다 . |
ExecutorService 작업에 대한 간단한 예를 살펴보겠습니다 .
import java.util.List;
import java.util.concurrent.*;
public class ExecutorServiceTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//Create an ExecutorService for 2 threads
java.util.concurrent.ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
// Create 5 tasks
MyRunnable task1 = new MyRunnable();
MyRunnable task2 = new MyRunnable();
MyRunnable task3 = new MyRunnable();
MyRunnable task4 = new MyRunnable();
MyRunnable task5 = new MyRunnable();
final List<MyRunnable> tasks = List.of(task1, task2, task3, task4, task5);
// Pass a list that contains the 5 tasks we created
final List<Future<Void>> futures = executorService.invokeAll(tasks, 6, TimeUnit.SECONDS);
System.out.println("Futures received");
// Stop the ExecutorService
executorService.shutdown();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(executorService.isShutdown());
System.out.println(executorService.isTerminated());
}
public static class MyRunnable implements Callable<Void> {
@Override
public void call() {
// Add 2 delays. When the ExecutorService is stopped, we will see which delay is in progress when the attempt is made to stop execution of the task
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("sleep 1: " + e.getMessage());
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println("sleep 2: " + e.getMessage());
}
System.out.println("done");
return null;
}
}
}
산출:
완료
퓨처가 수신한
수면 1: 수면 중단
수면 1: 수면 중단
완료
완료
참
참
각 작업은 5초 동안 실행됩니다. 우리는 두 개의 스레드에 대한 풀을 만들었으므로 처음 두 줄의 출력이 완벽하게 이해됩니다.
프로그램이 시작되고 6초 후에 invokeAll 메서드가 시간 초과되고 결과가 Futures 목록으로 반환됩니다 . 이것은 출력 문자열 Futures received 에서 볼 수 있습니다 .
처음 두 가지 작업이 완료되면 두 가지 작업이 더 시작됩니다. 하지만 invokeAll 메서드에 설정된 제한 시간이 경과하기 때문에 이 두 작업은 완료할 시간이 없습니다. 그들은 "취소" 명령을 받습니다. 이것이 출력에 sleep 1: sleep interrupted 가 포함된 두 줄이 있는 이유입니다 .
그런 다음 done 이 있는 두 줄을 더 볼 수 있습니다 . 이것이 invokeAll 메서드를 설명할 때 언급한 부작용입니다 .
다섯 번째이자 마지막 작업은 시작조차 되지 않으므로 출력에 아무것도 표시되지 않습니다.
마지막 두 줄은 isShutdown 및 isTerminated 메서드를 호출한 결과입니다 .
디버그 모드에서 이 예제를 실행하고 시간 제한이 경과한 후 작업 상태를 살펴보는 것도 흥미롭습니다( executorService.shutdown(); 을 사용하여 줄에 중단점을 설정 ).
두 개의 작업이 정상적으로 완료되고 세 개의 작업이 "취소됨" 인 것을 볼 수 있습니다 .
ScheduledExecutorService
실행자에 대한 논의를 마치기 위해 ScheduledExecutorService 를 살펴보겠습니다 .
4가지 방법이 있습니다.
방법 | 설명 |
---|---|
public ScheduledFuture<?> schedule(실행 가능한 명령, 긴 지연, TimeUnit 단위); | 전달된 Runnable 작업이 인수로 지정된 지연 후 한 번 실행되도록 예약합니다. |
public <V> ScheduledFuture<V> schedule(Callable<V> 호출 가능, 긴 지연, TimeUnit 단위); | 전달된 Callable 태스크가 인수로 지정된 지연 후 한 번 실행되도록 스케줄합니다. |
public ScheduledFuture<?> scheduleAtFixedRate(실행 가능한 명령, 긴 initialDelay, 긴 기간, TimeUnit 단위); | 전달된 작업의 주기적 실행을 예약합니다. 이 작업은 initialDelay 이후 처음으로 실행되고 각 후속 실행은 period 이후에 시작됩니다 . |
public ScheduledFuture<?> scheduleWithFixedDelay(실행 가능한 명령, 긴 initialDelay, 긴 지연, TimeUnit 단위); | initialDelay 이후 처음으로 실행될 전달된 작업의 주기적 실행을 예약 하고 각 후속 실행은 지연 (이전 실행 완료와 현재 실행 시작 사이의 기간) 후에 시작됩니다 . |