JAVA

스레드 풀(Thread Pool)과 Executor

yujin0517 2024. 1. 31. 23:04
목차
  1. 스레드 풀이란?
  2. 스레드 풀을 왜 사용해야 하는가?
  3. 자바는 스레드 풀을 어떻게 구현하는가?
  4. Executor 인터페이스를 왜 사용하는가?
  5. Executor 인터페이스에 대하여 
  6. Executor 동작 주기

 

스레드 풀은 동일하고 독립적인 다수의 작업을 실행할 때 가장 효과적이다. 

스레드 풀이란?

스레드 풀은 스레드를 생성하여 관리하는 곳이다. 생성된 스레드에 작업을 할당해 동시에 실행되는 작업을 관리한다. 스레드는 할당된 작업이 끝나면 스레드 풀로 돌아와 대기 상태가 되며, 다음 작업 할당을 위해 준비한다. 

스레드 풀의 스레드에 할당되는 작업의 종류 혹은 처리 시간이 비슷할수록 해당 스레드 풀의 작업 처리 속도가 빨라진다. 또한, 크기가 제한된 스레드 풀에 다른 작업과 의존성을 가지는 작업이 할당될 경우 데드락에 빠질 수도 있다. 

네트워크 기반의 서버 애플리케이션의 작업은 서로 동일하면서 독립적이다라는 조건을 만족한다. 예를 들어, 웹 서버는 각각의 요청에 대해 별도의 스레드를 할당하여 처리하기 때문에 작업 간의 의존성이 적고 스레드 풀을 효과적으로 사용할 수 있다. 

스레드 풀의 내부에는 작업을 쌓아두는 작업 큐(Work Queue)가 존재한다. 만약 스레드 풀의 모든 스레드가 작업 중에 있다면 할당된 작업은 큐에 쌓이게 되고, 대기 중인 스레드가 생기면 해당 스레드에 작업이 할당된다. 

이미지 출처 https://www.baeldung.com/thread-pool-java-and-guava


스레드 풀을 왜 사용해야 하는가?

자바 언어는 스레드를 운영체제의 자원으로 사용한다. 자바 언어로 구축된 서버를 사용하는 애플리케이션이 불특정 다수의 사용자가 사용한다고 가정했을 때, 운영체제는 작업 수행을 위해 스레드를 생성한다. 만약, 사용자가 몰려 작업의 양이 많아지면 그만큼 많은 스레드가 생성된다. 하지만 스레드가 무한대로 생성되면 서비스에 부하가 발생해 서버가 다운될 수도 있다. 서버가 다운되면 애플리케이션을 사용하는 사용자 입장에서도 운영하는 개발자 입장에서도 곤란한 상황이 발생하는 것이다. 

때문에 작업 수행을 위한 스레드를 미리 생성해 관리하는 스레드 풀을 사용한다. 위의 가정에 크기가 제한된 스레드 풀을 사용하면 스레드가 무한대로 생성되는 일이 없기 때문에 서버 다운의 걱정이 없다. 물론 작업 큐 마저 다 차버리면 서버 다운의 가능성이 있지만, 작업 큐의 크기를 조절해 주면 되기 때문에 서버 다운 가능성이 매우 낮다. 그리고 미리 생성된 스레드에 작업이 할당됨으로 작업 처리 시간도 줄어든다. 

→ 스레드 풀의 장점

  • 스레드 생성, 삭제에 따른 오버헤드가 줄어 자원을 효율적으로 관리
  • 불필요한 자원 소모 방지
  • 작업 요청이 발생했을 때 대기 중인 스레드에 작업을 할당함으로써 작업 처리 속도 향상
  • 스레드 풀 크기를 조절로 서비스 부하를 조절할 수 있어 성능 저하를 방지
  • 적절한 스레드 풀의 크기를 유지하면 놀고 있는 프로세서가 사라질 수 있음
  • 작업들은 스레드 풀에 만들어진 스레드를 사용하면 되기 때문에 경쟁이 사라짐

자바는 스레드 풀을 어떻게 구현하는가?

스레드 생성 및 종료에 따른 오버헤드를 줄여 자원을 효율적으로 사용하고, 서버 부하는 줄이기 위해 Java5부터 Executor 인터페이스를 제공한다. Executor 인터페이스를 사용해 스레드 풀을 구현한다. 


Executor 인터페이스를 왜 사용하는가?

Executor 프레임워크는 작업 등록과 작업 실행 부분을 분리한다. 작업 등록과 작업 실행을 분리하면 특정 작업을 실행할 때, 코드를 많이 변경하지 않아도 되고, 예외 상황을 마주하지 않으면서도 실행 정책을 쉽게 변경할 수 있다는 장점이 있다. 

Thread 클래스를 사용해 각각의 스레드를 생성하는 것보다 Executor 인터페이스를 사용해 스레드 풀을 만들어 웹 서버를 운영하는 것이 좋다는 것을 위에서 살펴봤다. 또, 하나의 이점이 있다면 인터페이스라는 것이다. 자바는 기본적으로 다중상속을 지원하지 않는다. 때문에 Thread 클래스를 상속받아 사용하면 다른 클래스의 기능을 사용하지 못한다. 하지만 Executor 인터페이스를 상속받아 사용하면 스레드 풀을 구현할 수 있을 뿐만 아니라 다른 클래스 혹은 다른 인터페이스를 상속받아 다양한 기능을 구현할 수 있다. (인터페이스는 다중 상속이 가능하다.)

그리고 자바 클래스 라이브러리에서 작업을 실행하고자 할 때, Thread 클래스보다 Executor 인터페이스가 훨씬 추상화가 잘되어 있기 때문에 사용하기 좋다. 


Executer 인터페이스에 대하여 

  • Executor는 작업 등록과 작업 실행을 분리하는 표준적인 방법을 사용하며, 각 작업은 Runnable의 형태로 정의한다. 
  • Executor는 단순한 인터페이스가 아닌, 다양한 종류의 작업의 실행 정책을 지원하는 유연하면서도 강력한 비동기적 작업 실행 프레임워크이다. 
  • Executor을 구현한 클래스는 작업의 라이프 사이클을 관리하는 기능도 가지고 있고, 몇 가지 통계 값을 뽑아내거나 애플리케이션에서 작업 실행 과정을 관리하고 모니터링하기 위한 기능도 가지고 있다. 
  • Executor의 구조는 프로듀서-컨슈머 패턴을 기반으로 한다. 작업을 생성해 등록하는 클래스가 프로듀서 역할을 하고, 작업을 실행하는 스레드가 컨슈머의 역할이다. 프로듀서-컨슈머 패턴을 애플리케이션에 적용해 구현할 수 있는 가장 쉬운 방법이 바로 Executor를 사용하는 방법이다. 

Executor의 동작 주기

Executor를 구현하는 클래스는 대부분 작업을 처리하는 스레드를 생성하도록 되어 있다. 하지만 JVM은 모든 스레드가 종료되기 전에는 종료하지 않고 대기하기 때문에 Executor가 제대로 종료되지 않으면 JVM 자체가 종료되지 않고 대기 상태가 된다. 

Executor는 작업을 비동기적으로 실행하기 때문에 작업 상태를 특정 시점에서 정확히 파악하기는 어렵다. Executor가 애플리케이션에 스레드 풀 등의 서비스를 제공한다는 관점으로 보면, Executor 역시 안전한 방법이건, 강제적인 방법이건 종료 절차를 거쳐야 한다. 

이처럼 서비스를 실행하는 동작 주기와 관련해 Executor를 상속받은 ExecutorService 인터페이스에는 동작 주기를 관리할 수 있는 메서드가 추가되어 있다. 

ExecutorService의 동작 주기에는 실행 중(Running), 종료 중(Shutting down), 종료(Terminate) 3가지 상태가 있다. ExecutorService를 처음 생성했을 때는 실행 상태가 된다. shutdown 메서드를 실행하면 안전한 종료 절차를 진행하면서 종료 상태로 들어간다.  shutdownNow 메서드를 실행하면 강제 종료 절차를 진행한다.

ExecutorService의 하위 클래스인 ThreadPoolExecutor는 이미 종료 절차에 들어간 이후에 새로운 작업을 등록하려 하면 실행 거절 핸들러를 통해 오류로 처리한다. 일반적으로 shutdown 메서드를 실행한 후 바로 awaitTerminated를 실행하면 마치 ExecutorService를 직접 종료시키는 것과 비슷한 효과를 얻을 수 있다.

 

  Executor  
  ExecutorService  
AbstractExecutorService   ScheduledExecutorService
ThreadPoolExecutor    
ScheduledThreadPoolExecutor    

Executor 인터페이스의 계층 구조

 

 

이 글은 '자바 병렬 프로그래밍' 교재와 아래 블로그를 참고하여 작성된 글입니다.

 

[JAVA] 쓰레드 풀(Thread Pool): 개념, 장점, 사용 방법, 코드 예시 (feat. Baeldung)

💋 쓰레드 풀의 필요성 서버는 동시에 여러 사용자가 접속할 수 있습니다. 자바에서는 스레드를 운영 체제의 자원으로 사용합니다. 우리가 스레드를 계속해서 만들면, 운영 체제의 자원이 빨리

engineerinsight.tistory.com

 

'JAVA' 카테고리의 다른 글

MVC 패턴, MVC 패턴의 한계  (0) 2024.02.08
JSP와 서블릿(Servlet) 그리고 MVC 패턴  (1) 2024.02.06
스레드(Thread)  (1) 2024.01.27
소수 찾기, 구하기  (0) 2023.02.01
[Java] 다형성(polymorphism)  (0) 2022.08.31