Executor 인터페이스가 필요한 이유는 무엇입니까?

Java 5 이전에는 애플리케이션에서 모든 자체 코드 스레드 관리를 작성해야 했습니다. 또한,새 스레드개체는 리소스를 많이 사용하는 작업이며 모든 경량 작업에 대해 새 스레드를 만드는 것은 이치에 맞지 않습니다. 그리고 이 문제는 멀티스레드 애플리케이션의 모든 개발자에게 친숙하기 때문에 이 기능을 Executor 프레임워크로 Java에 도입하기로 결정했습니다.

큰 아이디어는 무엇입니까? 간단합니다. 새 작업마다 새 스레드를 만드는 대신 스레드를 일종의 "저장소"에 보관하고 새 작업이 도착하면 새 작업을 만드는 대신 기존 스레드를 검색합니다.

이 프레임워크의 주요 인터페이스는 Executor , ExecutorServiceScheduledExecutorService 이며 각각 이전 인터페이스의 기능을 확장합니다.

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 메서드를 설명할 때 언급한 부작용입니다 .

다섯 번째이자 마지막 작업은 시작조차 되지 않으므로 출력에 아무것도 표시되지 않습니다.

마지막 두 줄은 isShutdownisTerminated 메서드를 호출한 결과입니다 .

디버그 모드에서 이 예제를 실행하고 시간 제한이 경과한 후 작업 상태를 살펴보는 것도 흥미롭습니다( 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 이후 처음으로 실행될 전달된 작업의 주기적 실행을 예약 하고 각 후속 실행은 지연 (이전 실행 완료와 현재 실행 시작 사이의 기간) 후에 시작됩니다 .