Spring에서 @Async로 비동기 처리를 해보자!

준커·2023년 10월 5일
0

Spring

목록 보기
3/3
post-thumbnail

1. 비동기 처리란?

일이 끝나기를 기다리지 않고, 다음 작업을 하는 것을 말합니다.

ex) A와 B가 은행 업무 처리를 위해 은행에 A -> B 순서로 도착했을 때.

  • A의 업무가 끝날 때까지 B가 기다린 뒤, A의 업무가 끝난 후에 B가 업무를 처리한다.
    -> 동기 처리.
  • A의 업무가 끝나지 않았더라도 B가 업무를 처리한다.
    -> 비동기 처리.

2. @Async

Spring에서 비동기 처리를 가능하게 해주는 @Async 어노테이션!

2-1. Application Class에 @EnableAsync 붙이기

AsyncApplication.class

@SpringBootApplication
@EnableAsync
public class AsyncApplication {

	public static void main(String[] args) {
		SpringApplication.run(AsyncApplication.class, args);
	}

}

2-2. 비동기 처리를 할 함수에 @Async 어노테이션 붙이기.

AsyncAService.class

@Service
public class AsyncAService {

    @Async
    public void testA(){
        System.out.println("A");
    }

	@Async
    public void testB() throws InterruptedException {
        Thread.sleep(3000);
        System.out.println("B");
    }


}
  • AsyncAService에서 비동기로 처리할 testA()와 testB() 정의합니다.
  • testA()와 testB()에 각각 @Async 어노테이션을 붙입니다.
  • 비동기로 처리가 잘 되는지 확인하기 위해 testB()에 3초의 Thread sleep을 걸어줍니다.

AsyncController.class

@RestController
@RequiredArgsConstructor
public class AsyncController {
    private final AsyncAService asyncAService;

    @GetMapping("")
    public void testAsync() throws InterruptedException {
        asyncAService.testB();
        asyncAService.testA();
    }
}

2-3. 결과

B가 먼저 실행됐음에도 A가 먼저 출력되는 것을 확인할 수 있습니다.

3. 주의사항 및 여러가지 Case

자가호출을 하면 안된다.
같은 클래스에서 호출을 하면 안 된다!

3-1. Case1 (실패)

AsyncAService.class

@Service
public class AsyncAService {

    @Async
    public void testA(){
        System.out.println("A");
    }

    @Async
    public void testB() throws InterruptedException {
        Thread.sleep(3000);
        System.out.println("B");
    }

    public void testC() throws InterruptedException {
        testB();
        testA();
    }
    
}

AsyncController.class

@RestController
@RequiredArgsConstructor
public class AsyncController {
    private final AsyncAService asyncAService;

    @GetMapping("")
    public void testAsync() throws InterruptedException {
        asyncAService.testC();
    }
}
  • 위와 같이 AsyncAService에서 testA()와 testB()를 호출하는 testC()를 만듭니다.

  • AsyncController에서 AsyncAService의 testC()를 호출합니다.

  • @Async 어노테이션을 붙였음에도 비동기로 처리가 되지 않고, testB()가 먼저 실행되고, 완료 후에 testA()가 실행됩니다.

3-2. Case2 (성공)

AsyncAService.class

@Service
@RequiredArgsConstructor
public class AsyncAService {
	private final AsyncBService asyncBService;
    
    @Async
    public void testA(){
        System.out.println("A");
    }

    public void testC() throws InterruptedException {
        asyncBService.testB();
        testA();
    }

}

AsyncBService.class

@Service
public class AsyncBService {

    @Async
    public void testB() throws InterruptedException {
        Thread.sleep(3000);
        System.out.println("B");

    }

}

AsyncController.class

@RestController
@RequiredArgsConstructor
public class AsyncController {
    private final AsyncAService asyncAService;

    @GetMapping("")
    public void testAsync() throws InterruptedException {
        asyncAService.testC();
    }
}
  • AsyncAService에 testA()를 AsyncBService에 testB()를 정의합니다.
  • AsyncAService에 testA()와 testB()를 호출하는 testC()를 호출합니다.
  • AsyncController에서 AsyncAService의 testC()를 호출합니다.

  • 해당 경우에선 정상적으로 비동기 처리가 되는 것을 확인할 수 있습니다.

3-3. Case3 (실패)

AsyncAService.class

@Service
public class AsyncAService {
    
    @Async
    public void testA(){
        System.out.println("A");
    }

}

AsyncBService.class

@Service
@RequiredArgsConstructor
public class AsyncBService {
	private final AsyncAService asyncAService;

    @Async
    public void testB() throws InterruptedException {
        Thread.sleep(3000);
        System.out.println("B");

    }
            
    public void testC() throws InterruptedException {
    	asyncBService.testB();
        testA();
    }

}

AsyncController.class

@RestController
@RequiredArgsConstructor
public class AsyncController {
    private final AsyncBService asyncBService;

    @GetMapping("")
    public void testAsync() throws InterruptedException {
        asyncBService.testC();
    }
}	
  • Case2와 반대로 testC()를 testB()가 있는 곳에 정의합니다.
  • AsyncController에서 AsyncBService의 testC()를 호출합니다.

  • 해당 경우에선 비동기 처리가 되지 않는 것을 확인할 수 있습니다.

3-4. Case4 (성공)

AsyncAService.class

@Service
public class AsyncAService {

    @Async
    public void testA(){
        System.out.println("A");
    }
    
    @Async
    public void testB() throws InterruptedException {
        Thread.sleep(3000);
        System.out.println("B");

    }

}

AsyncBService.class

@Service
@RequiredArgsConstructor
public class AsyncBService {
	private final AsyncAService asyncAService;
    
    public void testC() throws InterruptedException {
        asyncAService.testB();
        asyncAService.testA();
    }

}

AsyncController.class

@RestController
@RequiredArgsConstructor
public class AsyncController {
    private final AsyncBService asyncBService;

    @GetMapping("")
    public void testAsync() throws InterruptedException {
        asyncBService.testC();
    }
}

AsyncController.class

@RestController
@RequiredArgsConstructor
public class AsyncController {
    private final AsyncBService asyncBService;

    @GetMapping("")
    public void testAsync() throws InterruptedException {
        asyncBService.testC();
    }
}
  • AsyncAService에 testA()와 testB()를 모두 선언합니다.
  • AsyncBService에 testA()와 testB()를 호출하는 testC()를 선언합니다.
  • AsyncController에서 AsyncBService의 testC()를 호출합니다.

  • 해당 경우에선 정상적으로 비동기 처리가 되는 것을 확인할 수 있습니다.

4. Thread pool을 사용하는 방법

@Async를 Thread pool로 커스텀할 수 있습니다!

4-1. Application Class에 @EnableAsync를 제거하기

AsyncApplication.class

@SpringBootApplication
public class AsyncApplication {

	public static void main(String[] args) {
		SpringApplication.run(AsyncApplication.class, args);
	}

}

4-2. AsyncThreadConfig 생성하기

AsyncConfig.class

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "asyncThreadPoolExecutor")
    public Executor asyncThreadPoolExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(3);
        taskExecutor.setMaxPoolSize(30);
        taskExecutor.setQueueCapacity(100);
        taskExecutor.setThreadNamePrefix("Executor-");
        return taskExecutor;
    }

}
  • @EnableAsync를 Application Class가 아닌 Config Class에 붙여줍니다.
  • setCorePoolSize() -> 기본 스레드 수
  • setMaxPoolSize() -> 최대 스레드 수
  • setQueueCapacity() -> Queue 사이즈

4-3. @Async 어노테이션에 Thread 이름을 부여하기

AsyncAService

@Service
public class AsyncAService {
    @Async("asyncThreadPoolExecutor")
    public void testA(){
        System.out.println("A");
    }

    @Async("asyncThreadPoolExecutor")
    public void testB() throws InterruptedException {
        Thread.sleep(3000);
        System.out.println("B");

    }

}
  • 이렇게 하시면 Thread Pool을 이용한 커스텀이 가능합니다.

0개의 댓글