Java Executors 정리

Bruce Han·2023년 2월 12일
0

Java8-정리

목록 보기
14/20
post-thumbnail

이 포스팅의 코드 및 정보들은 강의를 들으며 정리한 내용을 토대로 작성한 것입니다.

Executors개요

Executors란?

스레드를 만들고 관리하는 작업을 좀 더 고수준의 API에게 일을 위임하는데, 그때 그 일을 대신 해주는 것

Executors가 하는 일

Executor가 스레드를 만들면 우리는 Runnable만 제공해주면 된다. 우리는 Runnable 안에다가 해야할 일만 정의해서 주면 되고, 스레드를 만들어서 실행하거나 필요없으면 종료시키면 된다.

주요 인터페이스

Executors

Executor 인터페이스는 사실상 쓸 일이 없고, ExecutorService를 자주 쓴다.

execute(Runnable)

실행하는 메서드이다.

ExecutorService

ExecutorService는 Executor를 상속받은 인터페이스이다.

shutdown, submit(Callable), submit(Runnable) 등의 메서드가 있다.

ScheduledExecutorService

ScheduledExecutorService는 ExecutorService를 상속받은 인터페이스이다.

schedule(Runnable, long, TimeUnit)

특정 시간 이후에 딜레이를 시키는 동안 어떤 작업을 하거나, 주기적으로 어떤 작업을 실행하고 싶을 때 사용할 수 있는 (함수형)인터페이스이다.

ExecutorService로 연습하기

public static void main(String[] args) {
	// static factory method 사용 가능
    ExecutorService executorService = Executors.newSingleThreadExecutor();
}
executorService.submit(new Runnable() { // execute()를 써도 됨
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getName());
    }
});
        executorService.submit(() -> System.out.println("Thread " + Thread.currentThread().getName()));

ExecutorService는 어떤 작업을 실행하고 나면 다음 작업이 들어올 때까지 계속 대기를 하기 때문에, 프로세스가 죽지 않으므로 명시적으로 셧다운을 해야 한다.

shutdown()

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
	System.out.println("Thread " + Thread.currentThread().getName());
});
executorService.shutdown(); // 작업을 처리한 다음에 종료하겠다, Graceful Shutdown

shutdown()은 프로세스를 우아하게 죽이는 것이다. Graceful Shutdown이라고도 하며, 현재 진행 중인 작업은 끝까지 마치고 끝내는 것이다.

shutdown()이 호출되고 나서 프로세스가 죽는 것을 볼 수 있다.

shutdown()
Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation has no additional effect if already shut down.
This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that.

이전에 접수된 실행된 작업순으로 종료시키지만, 새 작업은 받지 않는다. 이미 종료된 경우에서의 호출은 추가적인 효과가 없다.
이 메서드는 이전에 접수된 작업이 실행을 완료할 때까지 기다리지 않는다. 이를 위해서는 awaitTermination을 써라.

shutdownNow()

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
	System.out.println("Thread " + Thread.currentThread().getName());
});
executorService.shutdownNow(); // 작업을 즉시 종료하겠다.

shutdownNow()는 프로세스가 작업을 처리할 때까지 기다리지 않고 즉시 죽인다.

이것도 shutdown()과 마찬가지로 호출 시 프로세스가 종료되는 것을 볼 수 있다.

shutdownNow()
Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.
This method does not wait for actively executing tasks to terminate. Use awaitTermination to do that.
There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via Thread.interrupt, so any task that fails to respond to interrupts may never terminate.

실행 중인 모든 작업을 중지하려고 시도하고, 대기 중인 작업의 처리를 중지하며, 실행을 기다리고 있던 작업 목록을 반환한다.
이 메서드는 능동적으로 실행 중인 작업이 종료될 때까지 기다리지 않는다. 이를 위해서는 awaitTermination을 써라.
능동적으로 실행 중인 작업의 처리를 중지하려는 최선의 노력 외에는 어떠한 보장도 없다. 예를 들어, 일반적인 구현은 Thread.interrupt()를 통해 취소되므로 인터럽트에 응답하지 못하는 작업은 결코 종료되지 않을 수 있다.

놀고 있는 스레드가 다 종료된 후 끝낼지 안 끝낼지는 모르는 것이다.


ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
	System.out.println("Thread " + Thread.currentThread().getName());
});

위 소스를 실행시키면 스레드 풀(pool)이 생긴다. 스레드가 한 개이지만 Executors.newFixedThreadPool(2)로 두 개 이상을 만들 수도 있다.

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    executorService.submit(getRunnable("Hello"));
    executorService.submit(getRunnable("Spring"));
    executorService.submit(getRunnable("Java"));
    executorService.submit(getRunnable("ExecutorExample"));
    executorService.submit(getRunnable("Programming"));
        
    executorService.shutdown();
}

private static Runnable getRunnable(String message) {
    return () -> {
        System.out.println(message + " " + Thread.currentThread().getName());
    };
}

위 소스를 실행시키면 스레드 풀 2개를 가지고 번갈아가면서 실행될 것이다. (딜레이를 줘도 실행된다.)

이는 ExecutorService메서드를 실행할 때, Blocking Queue를 이용하여 은행 대기번호 뽑고 기다리듯이 스레드도 비슷하게 자원(자리)이 날 때까지 기다리다가 자리가 생기면 그때 큐에서 빠져나와 자원을 사용하는 것이다.


public static void main(String[] args) {
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    executorService.schedule(getRunnable("Hello "), 3, TimeUnit.SECONDS);
    executorService.shutdown();
}

private static Runnable getRunnable(String message) {
    return () -> {
        System.out.println(message + " " + Thread.currentThread().getName());
    };
}

ScheduledExecutorService로 받아야 해당하는 인터페이스가 제공하는 스케줄을 쓸 수 있다.
3초 정도 있다가 schedule()을 실행하고 shutdown()으로 종료하는 과정을 거칠 것이다.

만약 반복적으로 실행하고 싶으면 scheduleAtFixedRate()를 사용하여 1초 기다렸다가 2초에 한 번씩 더 "Hello " 출력되는 식으로 활용할 수 있다.

이때, shutdown() 이 있으면 안에 있는 task가 InterruptedException을 받기 때문에 종료될 수 있다.

Fork/Join Framework

Fork/Join은 Multi Threading 아니라 Multi Processor를 사용하는, 애플리케이션을 개발할 때 유용한 Framework이다.

정리

  • 고수준 (High-Level) Concurrency Programming
    • 스레드를 만들고 관리하는 작업을 애플리케이션에서 분리
    • 그런 기능을 Executors에게 위임한다
  • Executors가 하는 일
    • 스레드 만들기 : 애플리케이션이 사용할 스레드 풀을 만들어 관리한다.
    • 스레드 관리 : 스레드 생명 주기를 관리한다.
  • 주요 인터페이스
    • Executor: execute(Runnable)
    • ExecutorService: Executor를 상속 받은 인터페이스로, Callable도 실행할 수 있으며, Executor를 종료 시키거나 여러 Callable을 동시에 실행하는 등의 기능을 제공한다.
    • ScheduledExecutorService: ExecutorService를 상속 받은 인터페이스로 특정 시간 이후에 또는 주기적으로 작업을 실행할 수 있다.
  • ExecutorService로 작업 실행하기
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
	System.out.println("Hello : " + Thread.currentThread().getName());
});
  • ExecutorService로 멈추기
executorService.shutdown();	// 처리 중인 작업을 기다렸다가 종료
executorService.shutdownNow();	// 당장 종료
  • Fork/Join Framework
    • ExecutorService의 구현체로 손쉽게 멀티 프로세서를 활용할 수 있게끔 도와준다.

Reference

profile
만 가지 발차기를 한 번씩 연습하는 사람은 두렵지 않다. 내가 두려워 하는 사람은 한 가지 발차기를 만 번씩 연습하는 사람이다. - Bruce Lee

0개의 댓글