간단한 프로그램을 고려하십시오.

public static void main(String[] args) throws Exception {
	// Create an ExecutorService with a fixed number of threads: three
	ExecutorService service = Executors.newFixedThreadPool(3);

	// Pass a simple Runnable task to the ExecutorService
	service.submit(() -> System.out.println("done"));
}

프로그램을 실행하면 기대하는 콘솔 출력이 생성됩니다.

완료

그러나 이것은 일반적으로 IntelliJ IDEA에서 볼 수 있는 출력이 뒤따르지 않습니다.

종료 코드 0으로 프로세스 완료

우리는 보통 프로그램이 끝날 때 그것을 봅니다.

왜 그런 일이 발생합니까?

newFixedThreadPool() 메서드 에 대한 설명은 ExecutorService 를 사용하여 생성된 스레드가 명시적으로 중지될 때까지 계속 존재한다고 알려줍니다 . 즉, ExecutorService 에 작업을 전달했기 때문에 작업 을 실행하기 위한 스레드가 생성되었고 작업이 완료된 후에도 해당 스레드가 계속 존재합니다.

ExecutorService에서 중지

따라서 ExecutorService 를 "종료"(또는 중지)해야 합니다 . 우리는 이것을 두 가지 방법으로 할 수 있습니다:

  1. void shutdown() — 이 메서드가 호출된 후 ExecutorService는 작업 수락을 중지합니다. 이전에 ExecutorService 에 제출된 모든 작업은 계속 실행됩니다.

    public static void main(String[] args) throws Exception {
    ExecutorService service = Executors.newFixedThreadPool(3);
        	service.submit(() -> System.out.println("task 1"));
        	service.submit(() -> System.out.println("task 2"));
        	service.shutdown();
        	// A RejectedExecutionException will occur here
        	service.submit(() -> System.out.println("task 3"));
    }
  2. List<Runnable> shutdownNow() — 이 메서드는 현재 활성화된 작업을 중지하려고 시도합니다. 여전히 자신의 차례를 기다리고 있는 작업은 폐기되고 Runnables 목록으로 반환됩니다 .

    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i)));
        List<Runnable> runnables = service.shutdownNow();
        runnables.forEach(System.out::println);
    }

산출:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[완료되지 않음, 작업 = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[포장된 작업 = Test$$Lambda$16/0x0000000800b95040@70177ecd]]
java.util.concurrent .FutureTask@cc34f4d[완료되지 않음, 작업 = java.util.concurrent.Executors$RunnableAdapter@66a29884[포장된 작업 = Test$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf[완료되지 않음, 작업 = java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Wrapped task = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

프로세스가 종료 코드 0으로 완료됨

출력은 실행마다 다릅니다. 출력에는 두 종류의 라인이 있습니다.

  • 숫자는 ExecutorService 가 해당 작업을 처리했음을 의미하며 작업을 생성하는 데 사용한 목록의 번호를 표시합니다.

  • FutureTask 객체 에서 toString() 메서드를 호출한 결과입니다 . 이러한 개체는 ExecutorService 에 제출되었지만 처리되지 않은 작업입니다 .

출력에는 또 다른 흥미로운 뉘앙스가 있습니다. 이상적인 세상에서는 표시된 모든 숫자를 먼저 본 다음 FutureTask 개체를 볼 것입니다. 그러나 동기화 문제는 출력의 라인을 뒤죽박죽으로 만듭니다.

다른 방법

ExecutorService에는 중지와 관련된 몇 가지 추가 메서드가 있습니다.

  1. boolean awaitTermination(long timeout, TimeUnit unit) — 이 메서드는 자신을 호출하는 스레드를 차단합니다. 다음 세 가지 이벤트 중 하나가 발생하는 즉시 차단이 종료됩니다.

    • shutdown() 메서드가 호출된 후 모든 활성 작업과 예약된 작업이 모두 실행되었습니다.
    • 메서드 매개 변수에 의해 결정된 시간 제한이 경과했습니다.
    • awaitTermination() 메서드 를 호출한 스레드가 종료됩니다.

    이 메서드는 제한 시간이 경과하기 전에 ExecutorService가 중지된 경우 true를 반환 하고 제한 시간이 이미 경과한 경우 false를 반환 합니다.

    public static void main(String[] args) throws Exception {
    	ExecutorService service = Executors.newFixedThreadPool(2);
    	service.submit(() -> System.out.println("task 1"));
    	service.submit(() -> System.out.println("task 2"));
    	service.submit(() -> System.out.println("task 3"));
    	service.shutdown();
    	System.out.println(service.awaitTermination(1, TimeUnit.MICROSECONDS));
    }
  2. boolean isShutdown()ExecutorService 에서 shutdown() 또는 shutdownNow() 메서드가 호출된 경우 true를 반환합니다 .

    public static void main(String[] args) throws Exception {
    	ExecutorService service = Executors.newFixedThreadPool(2);
    	service.submit(() -> System.out.println("task 1"));
    	service.submit(() -> System.out.println("task 2"));
    	service.submit(() -> System.out.println("task 3"));
    	System.out.println(service.isShutdown());
    	service.shutdown();
    	System.out.println(service.isShutdown());
    }
  3. boolean isTerminated()ExecutorService 에서 shutdown () 또는 shutdownNow() 메서드가 호출되고 모든 작업이 완료되면 true를 반환합니다.

    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i)));
        service.shutdownNow();
        System.out.println(service.isTerminated());
    }

다음 메서드를 사용하는 예제 코드:

public static void main(String[] args) throws Exception {
   ExecutorService service = Executors.newFixedThreadPool(16);
   Callable<String> task = () -> {
       Thread.sleep(1);
       return "Done";
   };

   // Add 10,000 tasks to the queue
   List<Future<String>> futures = IntStream.range(0, 10_000)
           .mapToObj(i -> service.submit(task))
           .collect(Collectors.toList());
   System.out.printf("%d tasks were submitted for execution.%n", futures.size());

   // Attempt to shut down
   service.shutdown();
   // Wait 100 milliseconds to finish the work
   if (service.awaitTermination(100, TimeUnit.MILLISECONDS)) {
       System.out.println("All tasks completed!");
   } else {
       // Stop forcibly
       List<Runnable> notExecuted = service.shutdownNow();
       System.out.printf("%d tasks were not started.%n", notExecuted.size());
   }

   System.out.printf("Total tasks completed: %d.%n", futures.stream().filter(Future::isDone).count());
}

출력(실행마다 다름):

실행을 위해 10,000개의 작업이 제출되었습니다.
9170 작업이 시작되지 않았습니다.
완료된 총 작업: 830개 작업.

종료 코드 0으로 프로세스가 완료되었습니다.