관리 메뉴

개발 기록이

[JAVA] ExecutorService 비동기 처리 본문

웹 개발/Back-end

[JAVA] ExecutorService 비동기 처리

studyingbackhoe 2025. 1. 19. 14:12

여러 사람에게 메일 발송을 하는 것처럼 반복적인 작업을 동기 방식으로 처리하면, 각 작업이 완료될 때까지 블로킹이 발생하여 전체 처리 시간이 늘어날 수 있다. ExecutorService를 활용한 비동기 처리에 대해 알아보자.
 

병렬 작업과 스레드풀

  • 병렬 작업(여러 작업을 동시에 처리) 증가 → 스레드 개수 증가 → CPU, 메모리 사용량성능 저하
  • 병렬 작업 시 스레드풀을 사용하여 스레드 수 제어가 필요하다.

 

스레드풀(ThreadPool)의 동작

  • 제한된 개수의 스레드를 사용하여, 작업이 끝난 후에는 해당 스레드를 재사용하여 성능 저하를 막아준다.

 

자바에서 스레드풀 사용

  • ExecutorService 인터페이스를 통해 스레드풀을 관리한다.
  • Executors 클래스를 사용하여 다양한 유형의 스레드풀을 생성한다.

* Executors의 다양한 정적 메서드를 이용해서 ExecutorService 구현 객체를 만들 수 있다.

 
 

1. 스레드풀 생성

메소드명 초기 스레드 수 코어 스레드 수 최대 스레드 수
newCachedThreadPool() 0 0 Integer.MAX_VALUE
newFixedThreadPool(int n Threads) 0 nThreads nThreads

 

* 초기 스레드 수 : ExecutorService 객체가 생성될 때 기본적으로 생성되는 스레드 수.
* 코어 스레드 수 : 스레드 수가 증가된 후 사용되지 않는 스레드를 스레드풀에서 제거할 때 최소한 유지해야 할 스레드 수.

ExecutorService executorService = Executors.newCachedThreadPool();

ExecutorService executorService2 = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

 

2. 스레드풀 종료

스레드풀의 스레드는 데몬 스레드가 아니기 때문에 main 스레드가 종료되더라도 작업을 처리하기 위해 계속 실행 상태로 남아있다,. 그래서 main() 매소드가 실행이 끝나도 애플리케이션 프로세스는 종료되지 않는다. 애플리케이션을 종료하려면 스레드풀을 종료시켜 스레드들이 종료 상태가 되도록 처리해줘야 한다.

executorService.shutdown();    // 남아있는 작업을 마무리하고 스레드풀 종료
executorService.shutdownNow(); // 남아있는 작업과 상관없이 강제종료

 

3. 작업 처리 요청

exceute() 

  • 작업 처리 결과를 받지 못함.
  • 작업 처리 도중 예외가 발생하면 스레드가 종료되고 해당 스레드는 스레드풀에서 제거된다.

submit()

  • 작업 처리 결과를 받을 수 있도록 Future를 리턴.
  • 작업 처리 도중 예외가 발생하더라도 스레드는  종료되지 않고 다음 작업을 위해 재사용된다.
  • 스레드의 생성 오버헤더를 줄이기 위해서 submit()을 사용하는 것이 더 효율적이다.
executorService.execute();
executorService.submit();

 
 

예시코드

package threadTest;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadTest {

    public static void main(String[] args) {
        
        ExecutorService executorSv = Executors.newCachedThreadPool();
        
        // 요청1) API 요청
        executorSv.submit(() -> {
            processMessage("요청1) API 요청 시작");
            String apiResponse = apiRequest();
            processMessage("요청1-결과) API 요청 완료: " + apiResponse);
        });
        
        // 요청2) DB 쿼리 실행
        executorSv.submit(() -> {
            processMessage("요청2) DB 쿼리 시작");
            String queryResult = executeDbQuery();
            processMessage("요청2-결과) DB 쿼리 결과: " + queryResult);
        });
        
        // 요청3) 파일 저장 
        processMessage("요청3) 파일 저장 시작");
        boolean fileSaved = saveFile("sample.txt");
        if (fileSaved) {
        	processMessage("요청3-결과) 파일 저장 완료");
        } else {
        	processMessage("요청3-결과) 파일 저장 실패");
        }
        executorSv.shutdown();
    }
    
    private static String apiRequest() {
        try {
            Thread.sleep(3000); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "API 응답 데이터입니다.";
    }

    private static String executeDbQuery() {
        try {
            Thread.sleep(1500); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "DB 쿼리 실행 결과입니다.";
    }

    private static boolean saveFile(String fileName) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    
    // static 메서드: 객체 생성 없이 호출 가능
    private static void processMessage(String content) {
        System.out.println(Thread.currentThread().getName() + " :::::::: " + content);
    }
}

 

* 실행순서 : 요청3) 파일 저장 -> 요청2) DB 쿼리 시작 -> 요청1) 파일 저장

 
1) 비동기 작업:

  • "요청1) API 요청" 과 "요청2) DB 쿼리" 실행이 ExecutorService를 이용해 비동기로 처리된다. 각각 별도의 스레드에서 실행되고 2초, 1.5초 후에 결과가 출력된다.

2) 비동기 작업:  

  • "요청3) 파일 저장"은 메인 스레드에서 동기적으로 실행되며, 1초 후에 결과가 출력된다.

 

실행 결과

 


참고: 이것이 자바다(한빛미디어) - 12.9 스레드풀
https://velog.io/@pllap/Java에서의-비동기-프로그래밍
OpenAI ChatGPT (https://openai.com)