Gidhub BE Developer

Spring Boot @Async 동작 원리 : @Async를 선언했는데 왜 Async로 동작하지 않을까?

2021-02-23
goodGid

Prologue

  • 이번 글에서는

    @Async로 method를 선언했지만

    Async로 동작하지 않는 이유에 대해 알아본다.


@Async

AsyncCaller

@Component
@EnableAsync
public class AsyncCaller {

    @Autowired
    AsyncMailTrigger asyncMailTrigger;

    public void rightWayToCall() {
        System.out.println("[rightWayToCall] Thread Name :: " + Thread.currentThread().getName());
        asyncMailTrigger.sendMail();
    }

    public void wrongWayToCall() {
        System.out.println("[wrongWayToCall] Thread Name :: " + Thread.currentThread().getName());
        AsyncMailTrigger asyncMailTriggerObject = new AsyncMailTrigger(); // [1]
        asyncMailTriggerObject.sendMail();
    }
}

AsyncMailTrigger

@Component
public class AsyncMailTrigger {
    @Async
    public void sendMail() {
        System.out.println("[sendMail] Thread Name :: " + Thread.currentThread().getName());
    }
}

Output

// rightWayToCall( ) 호출 시
[rightWayToCall] Thread Name :: http-nio-8080-exec-2
[sendMail]       Thread Name :: task-2

// wrongWayToCall( ) 호출 시
[wrongWayToCall] Thread Name :: http-nio-8080-exec-2
[sendMail]       Thread Name :: http-nio-8080-exec-2
  • rightWayToCall( ) method는 다른 Thread가 처리하지만

    wrongWayToCall( ) method는 동일한 Thread가 처리한다.

  • 이유는 다음과 같다.

Here I created two methods 
rightWayToCall( ) used the @Autowired version of AsyncMailtrigger 
which will be picked by @ComponentScan

but in a wrongWayToCall( )
I create the object in local (= [1])
so it will not be picked up by @ComponentScan
it will not spawn a new thread 
and will be executed inside the main thread.

같은 클래스에서 @Async 호출하기

AsyncCallers

@Component
@EnableAsync
public class AsyncCaller {

    public void wrongWayToCallInSameClass() {
        System.out.println("[wrongWayToCallInSameClass] Thread Name :: " + Thread.currentThread().getName());
        sendMailInSameClass();
    }

    @Async
    public void sendMailInSameClass() {
        System.out.println("[sendMailInSameClass] Thread Name :: " + Thread.currentThread().getName());
    }
}

Output

[wrongWayToCallInSameClass] Thread Name :: http-nio-8080-exec-1
[sendMailInSameClass]       Thread Name :: http-nio-8080-exec-1
  • 같은 클래스에서 @Async method를 호출하면

    Async 하게 동작하기를 기대했지만 그렇지 않다.

  • 이유는 다음과 같다.

Never write an Async method in the same class 
where the caller method invokes the same Async methodAsync method 
in the same class where the caller method invokes the same Async method. 

So, always remember that 
when using this reference
Async does not work.

Because although it creates a proxy
the call bypasses the proxy 
and directly call the method 
so that Thread will not be spawned. 

This will prevent the developer from having the wrong assumption 
that it will work in an Async fashion. 

Most developers carelessly implement Async in this way, 
so be very careful when writing the Async caller method. 

It should be in different class when calling the Async method.

  • 위 답변과 필자가 알고 있는 개념을 통해 정리하자면 다음과 같다.

    ( 참고로 틀릴 수 있습니다. )

    ( 그러니 잘못된 부분이 있으면 꼭 피드백 부탁드립니다 ! )

  • 기본적으로 Async하게 동작하기 위해선 Spring의 도움이 필요하다.

  • 만약 Async하게 동작하기를 희망하는 요청이 있다면

    그 요청을 처리할 수 있는 Bean을

    Spring이 중간에 Proxy 객체로 Wrapping 하여 Async로 동작할 수 있게 도와준다.

  • 하지만 Spring Container에 있는 Bean을 사용하지 않거나

    (= new AsyncMailTrigger( ) )

    Direct로 접근하게 되면

    (= sendMailInSameClass( ) )

    Spring의 도움을 받을 수 없으므로 Async하게도 동작할 수 없게 된다.

Because although it creates a proxy
the call bypasses the proxy 
and directly call the method 
so that Thread will not be spawned. 

Summary

Q. 같은 클래스에서 호출하면 왜 Async 하게 동작하지 않는가?

  • Spring이 생성해주는 Proxy 를 사용하지 않기 때문이다. 라고 대답할 수 있겠다.

Reference


Comments

Index