본문 바로가기

JAVA

자바에서 스레드와 스레드풀 활용

서비스를 구현하다면 성능 측면에서의 효과를 극대화하기 위해 메서드 레벨에서 명시적인 방식으로 멀티스레드를 구현할 필요가 있다.

대용량 데이터의 처리 시간을 줄이기 위해 데이터를 분할 후 병렬로 처리
UI를 가지고 있는 애플리케이션에서 네트워크 통신을 위해 사용
다수 클라이언트의 요청을 처리하는 서버를 개발할 때 사용
특히, 외부 서비스를 반복적으로 호출해야하는 경우, 실행 순서에는 상관없이 여러 번 동일한 메서드를 호출해서 결과값을 Aggregation을 해야할 필요가 있다면 Single Thread 방식과 Multi Thread에 따라 효과 차이가 더 극명하게 드러난다.

배경 지식

프로세스와 스레드

운영체제에서는 실행 중인 하나의 애플리케이션을 프로세스(process) 라고 부른다.
사용자가 애플리케이션을 실행하면 운영체제로부터 실행에 필요한 메모리를 할당받아 애플리케이션의 코드를 실행하는데 이것이 프로세스이다.
( 하나의 애플리케이션은 다중 프로세스를 만들기도 한다.
ex - 크롬브라우저를 두 개 실행한다면 두 개의 크롬 프로세스가 생성된다.)

멀티 프로세스와 멀티 스레드의 차이

  • 멀티 프로세스 : 애플리케이션 단위의 멀티 태스킹(multi tasking)
  • 멀티 스레드 : 애플리케이션 내부에서의 멀티 태스킹
  • 멀티 프로세스
    • 프로세스 1
      • 멀티 스레드
    • 프로세스 2
      • 싱글 스레드
    • 프로세스 3
      • 싱글 스레드
    • 프로세스 4
      • 멀티 스레드

멀티 프로세스들은 운영체제에서 할당받은 자신의 메모리를 가지고 실행하기 때문에 서로 독립적이다. 따라서 하나의 프로세스에서 오류가 발생해도 다른 프로세스에게 영향을 미치지 않는다.
하지만 멀티 스레드는 하나의 프로세스 내부에 생성되기 떄문에 하나의 스레드가 예외를 발생시키면 프로세스 자체가 종료될 수 있어 다른 스레드에게 영향을 미치게 된다.

ThreadPool

스레드를 미리 만들어 놓고 재사용하는 것으로, 스레드 생성 비용(CPU, MEMORY)을 감소 할 수 있다.
스레드 풀은 스레드 풀 매니저에 의해 관리되며, 스레드 큐에 쌓인 스레드를 적절히 처리한다.


본문

멀티스레드 기반의 실행(Execute)과 종료(Shutdown) 등의 관리를 편하게 할 수 있도록 도와주는 라이브러리를 사용해서 좀 더 쉽고 직관적으로 멀티스레드 기반의 로직을 구현할 수 있다.

ExcutorService, Executors

Executors

Executor, ExecutorService, ScheduledExecutorService, ThreadFactory, Callable 등 멀티 쓰레드 기반으로 실행되는 인터페이스들의 팩토리 클래스입니다.
특히 ThreadPoolExecutor 생성자를 사용해서 여러 옵션의 스레드풀 기반 ExecutorService의 구현체를 제공해 주기 때문에 멀티 스레드 구현을 위해서는 대부분 아래의 방식으로 ExecutorService 구현체를 생성해서 여러 테스크들을 동시에 실행시킬 수 있다.

ExecutorService executorService = Executors.newCachedThreadPool();

ExecutorService Task 할당

ExecutorService를 초기화했다면 ThreadPool에 원하는 Task(작업)을 할당해야 한다.
Task를 Callable / Runnable 인터페이스를 구현하여 생성하고 ExecutorService의 메서드를 호출하여 실행한다.


Task 인터페이스 구현
Runnable runnableTask = () -> {---};
Callable callableTask = () -> {---};

ExecutorService 메서드 호출

작업(Task)을 할당(실행)하기 위해서 제공되는 메서드

  1. execute() - 리턴타입이 void로, Task의 실행결과나 Task의 상태(실행 중 or 실행 완료)를 알 수 없다.
executorService.execute(runnableTask);

2. submit() - Task를 할당하고 `Future` 타입의 결과값을 받는다. 결과가 리턴되어야 하므로 주로 Callabled을 구현한 Task를 인자로 준다.
Future future = executorService.submit(callableTask);

3. invokeAny() : Task를 Collection에 넣어서 인자로 넘겨줄 수 있다. 실행에 성공한 Task 중 하나의 리턴값을 반환한다.
String result = executorService.invokeAny(callableTasks);

4. invokeAll() : Task를 Collection에 넣어서 인자로 넘겨줄 수 있다. 모든 Task의 리턴값을 List> 타입으로 반환한다.
List<Future> futures = executorService.invokeAll(callableTasks);

ExecutorService 종료

실행 명령한 Task가 모두 수행되어도 ExecutorService는 자동으로 종료되지 않는다.
앞으로 들어올 Task를 처리하기 위해 Thread는 wait 상태로 대기한다. 그러므로 종료를 위해서는 제공되는 shutdown()이나 shutdownNow() API를 사용해야 한다.

  1. executorService.shutdown()

실행중인 모든 Task가 수행되면 종료한다.


2. List notExecutedTasks = executorService.shutDownNow()

실행 중인 Thread들을 즉시 종료시키려고 하지만 모든 Thread가 동시에 종료되는 것을 보장하지는 않고 실행되지 않은 Task를 반환한다.

스레드풀?

효과적적으로 스레드를 사용하기 위해, 스레드풀을 사용한다. 하지만 너무 많은 스레드를 관리하는 스레드풀을 생하면 작업이 없어 노는 스레드(Idle 상태)가 발생한다. 이는 리소스 낭비로 이어진다. 따라서 필요없는 스레드를 만들지 않는 여러 방법이 있다.

newFixedThreadPool(int nThreads)

지정한 수만큼의 고정된 스레드풀을 생성한다.

  • 항상 비슷하게 일이 있을 때 사용
    초기 스레드 개수는 0 개이며, nThreads의 값으로 코어 스레드 수와 최대 스레드 수를 지정할 수 있다.
    이 스레드풀은 스레드 개수보다 작업 개수가 많으면 스레드를 새로 생성하여 작업을 처리한다.
    Idle 상태이더라도 스레드를 제거하지 않고 내버려 둔다.

newSecheduledThreadPool(int corePoolSize)

일정 시간 뒤에 실행되는 작업이나, 주기적으로 수행되는 스레드풀을 corePoolSize인자 개수만큼 생성한다.
Idle 상태이더라도 corePoolSize만큼의 풀사이즈를 계속 유지한다.

newCachedThreadPool()

유기적으로 스레드의 숫자가 증가하고 감소하는 스레드풀
초기 스레드 개수, 코어 스레드 개수 0
최대 스레드 수는 Integer 데이터 타입이 가질 수 있는 최대 값
스레드 개수보다 작업 개수가 많으면 새로운 스레드를 생성하여 작업을 처리하고
60초 동안 작업이 없을 경우 스레드를 종료시키고 스레드풀에서 제거한다.
Cached라는 이름이 붙은대로 이미 생성된 쓰레드는 Reuse하기 때문에 성능상의 이점을 좀 더 기대할 수 있다.

내용 참조:

[https://frontdev.tistory.com/entry/Asynchronous-Multi-thread-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0\\\][https://frontdev.tistory.com/entry/Asynchronous-Multi-thread-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0\\\]]https://frontdev.tistory.com/entry/Asynchronous-Multi-thread-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0%5C%5D%5Bhttps://frontdev.tistory.com/entry/Asynchronous-Multi-thread-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0%5C%5D) [https://yonguri.tistory.com/136\](https://yonguri.tistory.com/136) [https://velog.io/@kkw9312/javaMultiThread\][https://velog.io/@kkw9312/javaMultiThread\]