ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 비동기 프로그래밍 구현
    [공부] 프로그래밍/Spring・Spring Boot (JAVA) 2024. 5. 20. 12:42

     
    ♧ 전체 코드 : https://github.com/woodisco/Async

    GitHub - woodisco/Async: async 공부

    async 공부. Contribute to woodisco/Async development by creating an account on GitHub.

    github.com

     
    Spring에서 비동기 프로그래밍을 하기 위해서는 ThreadPool을 정의할 필요가 있다. 
    ThreadPool을 생성해야 하는 이유?
    => 비동기는 Main Thread가 아닌 Sub Thread에서 작업을 진행하며 Java에서는 ThreadPool을 생성하여 Async 작업을 처리한다.
    ▷ ThreadPool 생성 옵션

    • CorePoolSize : 최소한의 Thread를 몇개를 가기고 있을 것이냐를 지정하는 옵션
    • MaxPoolSize : 최대 몇 개까지 Thread를 할당할 것인가를 지정하는 옵션
    • KeepAliveTime : 지정한 시간만큼 Thread들이 작동하지 않으면 반환하는 옵션
    • WorkQueue : 자료구조인 큐를 사용하여 WorkQueue라는 곳에 많은 요청을 보관하는 옵션

    ▷ ThreadPool 생성시 주의사항

    • CorePoolSize 값을 너무 크게 설정할 경우 side effect 고려하기
    • IllegalArgumentException 주의 : 
    CorePoolSize < 0
    KeepAliveTime < 0
    MaximumPoolSize <= 0
    MaximumPoolSize < CorePoolSize
    • NullPointerException : WorkQueue is Null            

    ▷ ThreadPool 정리

    if (Thread 수 < CorePoolSize)
        new Thread 생성
    
    if (Thread 수 > CorePoolSize)
        Queue에 요청 추가
        
    if (Queue Full && Thread 수 > MaxPoolSize)
        요청 거절
    
    if (Queue Full && Thread 수 < MaxPoolSize)
        new Thread 생성

    비동기 프로그래밍 구현

    ① AppConfig 작성

    @Configuration
    public class AppConfig {
    
        @Bean(name = "defaultTaskExecutor", destroyMethod = "shutdown")
        public ThreadPoolTaskExecutor defaultTaskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(200);
            executor.setMaxPoolSize(200);
    
            return executor;
        }
    
        @Bean(name = "messagingTaskExecutor", destroyMethod = "shutdown")
        public ThreadPoolTaskExecutor messagingTaskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(200);
            executor.setMaxPoolSize(200);
    
            return executor;
        }
    }
    destroyMethod = "shutdown"
    Bean이 소멸될 때 shutdown 메서드를 호출하여 스레드 풀을 안전하게 종료한다. 이는 스레드 풀이 더 이상 새로운 작업을 받지 않게 하고, 현재 진행 중인 작업이 완료될 때까지 기다렸다가 스레드를 종료한다.

    ② AsyncConfig 작성

    @Configuration
    @EnableAsync
    public class AsyncConfig {
    }

    Spring의 비동기 처리 기능을 활성화하는 애노테이션이다. 이를 통해 Spring은 @Async 애노테이션이 붙은 메서드들을 백그라운드 스레드에서 비동기적으로 실행할 수 있다.
    ③ AsyncController 작성
    ④ EmailService 작성

    @Service
    @RequiredArgsConstructor
    public class EmailService {
        @Async("defaultTaskExecutor")
        public void sendMail() {
            System.out.println("[sendMail] :: "
                    + Thread.currentThread().getName());
        }
    
        @Async("messagingTaskExecutor")
        public void sendMailWithCustomThreadPool() {
            System.out.println("[sendMailWithCustomThreadPool] :: "
                    + Thread.currentThread().getName());
        }
    }
    @Async 을 사용할때에는 무조건 public을 사용해야 한다. private 을 사용시 에러 발생.

    ⑤ AsyncService 작성

    @Service
    @RequiredArgsConstructor
    public class AsyncService {
        private final EmailService emailService;
    
        // ① Bean을 주입받은 경우 : 비동기 프로그래밍 가능
        public void asyncCall_1() {
            System.out.println("[asyncCall_1] :: " + Thread.currentThread().getName());
            emailService.sendMail();
            emailService.sendMailWithCustomThreadPool();
        }
    
        // ② 인스턴스를 이용한 경우 : 비동기 프로그래밍 불가능
        public void asyncCall_2() {
            System.out.println("[asyncCall_2] :: " + Thread.currentThread().getName());
            EmailService emailService = new EmailService();
            emailService.sendMail();
            emailService.sendMailWithCustomThreadPool();
        }
    
        // ③ 내부의 @Async를 이용한 경우 : 비동기 프로그래밍 불가능
        public void asyncCall_3() {
            System.out.println("[asyncCall_3] :: " + Thread.currentThread().getName());
            sendMail();
        }
    
        @Async
        public void sendMail() {
            System.out.println("[sendMail] :: " + Thread.currentThread().getName());
        }
    }

    curl localhost:8080/1 을 실행하면 아래와 같이 로그가 출력되며, 비동기를 구현할 수 있다.

    하지만 curl localhost:8080/2 혹은 curl localhost:8080/3 의 경우에는 아래와 같이 로그가 출력되며, 비동기를 구현 할 수 없게 된다.

    비동기를 구현할 때에는 무조건 Bean을 주입받은 형태로 구현을 해야 한다.
    그 이유는 Spring Framework가 비동기로 처리하고자 하는 메서드를 즉 EmailService를 Bean을 등록해서 순수한 Bean을 AsyncService에 반환을 해주는 것이 아니라 Async 하게 동작을 해야 하기 때문에 한번 더 wrapping을 해서 반환을 하게 된다.
    프록시 객체로 wrapping을 한 후 프록시 객체를 반환하게 된다. 그럼 AsyncService에서는 비동기로 처리할 수 있게 emailService.sendMail() 처리를 Sub Thread에게 위임을 하게 된다.

    ※ 프록시 객체 :
    프록시 객체는 원래 객체와 동일한 인터페이스를 구현하거나 상속받아, 원래 객체의 메서드 호출을 가로채서 처리할 수 있는 객체이다. 프록시를 사용하면 메서드 호출 전후에 추가 로직을 삽입할 수 있다.

     

    출처 : 패스트캠퍼스 10개 프로젝트로 완성하는 백엔드 웹개발(Java/Spring) 초격차 패키지 Online

    '[공부] 프로그래밍 > Spring・Spring Boot (JAVA)' 카테고리의 다른 글

    Feign Client 구현 - 2  (0) 2024.05.21
    Feign Client 구현 - 1  (0) 2024.05.21
    Spring Multi Module 구현  (0) 2024.05.16
    Spring Batch 구현 - 7  (0) 2024.05.14
    Spring Batch 구현 - 6  (0) 2024.05.09
Designed by Tistory.