동기/비동기 처리 : @Async/@Await

dakcoh·2024년 9월 29일

안녕하세요.
이번 시간에는 동기(Synchronous)와 비동기(Asynchronous)의 개념에 대해서 공부해보겠습니다.

이 두 가지는 작업의 실행 흐름과 처리 방식을 결정하는 핵심 요소로, 애플리케이션의 성능과 사용자 경험에 큰 영향을 미칩니다.

이번 포스팅에서는 동기와 비동기의 정의와 차이점부터, Spring에서 제공하는 @Async 어노테이션의 활용법까지 공부해보겠습니다.


동기와 비동기의 정의

동기(Synchronous)

  • 작업이 순차적으로 실행됩니다.
  • 현재 작업이 완료되기 전에는 다음 작업이 시작되지 않습니다.
  • 한 작업이 오래 걸리면 전체 프로세스가 지연될 수 있습니다.

Ex) 전화 통화: 상대방이 응답할 때까지 기다려야 대화가 가능합니다.

비동기(Asynchronous)

  • 작업이 병렬적으로 실행됩니다.
  • 현재 작업이 완료되기를 기다리지 않고 다음 작업을 바로 시작합니다.
  • 작업이 완료되면 콜백(Callback)이나 알림(Notification) 형태로 결과를 처리합니다.

Ex) 문자 메시지: 메시지를 보내고 즉시 다른 작업을 수행할 수 있으며, 상대방의 답장을 기다리지 않아도 됩니다.


동기와 비동기의 차이점

특징동기(Synchronous)비동기(Asynchronous)
작업 흐름순차적으로 실행병렬적으로 실행
대기 시간작업 완료까지 대기대기하지 않음
성능오래 걸리는 작업 시 병목 발생 가능병렬 처리로 효율성 증가
사용 사례데이터베이스 트랜잭션, 간단한 연산파일 처리, 외부 API 호출, 비동기 이벤트

Java에서의 동기 처리

Java에서는 기본적으로 메서드가 호출되면 해당 메서드가 완료될 때까지 호출한 스레드가 대기하는 동기 방식을 사용합니다.

public void syncExample() {
    System.out.println("첫 번째 작업 시작");
    performTask();
    System.out.println("두 번째 작업 시작");
}

private void performTask() {
    try {
        Thread.sleep(2000); // 2초 대기
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("작업 완료");
}

실행 결과

첫 번째 작업 시작
작업 완료
두 번째 작업 시작

Java에서의 비동기 처리

Java에서는 CompletableFuture와 같은 API를 사용하여 비동기 작업을 구현할 수 있습니다. 비동기는 작업의 결과를 기다리지 않고 다음 작업을 실행합니다.

public void asyncExample() {
    System.out.println("첫 번째 작업 시작");
    CompletableFuture.runAsync(() -> performTask());
    System.out.println("두 번째 작업 시작");
}

실행 결과

첫 번째 작업 시작
두 번째 작업 시작
(2초 후) 작업 완료

Spring에서의 비동기 처리: @Async

@Async 설정

Spring에서 @Async를 사용하려면 @EnableAsync를 추가해야 하며, 비동기 작업을 처리할 스레드풀(Executor)을 구성해야 합니다.

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("AsyncExecutor-");
        executor.initialize();
        return executor;
    }
}

@Async 사용 예제

비동기 메서드 정의:

@Service
public class AsyncService {

    @Async
    public CompletableFuture<String> performAsyncTask() {
        System.out.println("비동기 작업 시작: " + Thread.currentThread().getName());
        try {
            Thread.sleep(2000); // 2초 대기
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return CompletableFuture.completedFuture("작업 완료");
    }
}

사용:

@RestController
@RequestMapping("/api")
public class AsyncController {

    private final AsyncService asyncService;

    public AsyncController(AsyncService asyncService) {
        this.asyncService = asyncService;
    }

    @GetMapping("/async")
    public ResponseEntity<String> executeAsync() {
        asyncService.performAsyncTask()
            .thenAccept(result -> System.out.println("결과: " + result));
        return ResponseEntity.ok("비동기 요청 완료");
    }
}

실행 흐름:
1. 클라이언트가 /api/async에 요청을 보냅니다.
2. performAsyncTask는 별도의 스레드에서 실행됩니다.
3. 메인 스레드는 즉시 "비동기 요청 완료"를 반환합니다.
4. 2초 후, "작업 완료"가 출력됩니다.


@Async를 사용할 때 주의할 점

  1. 리턴 타입: @Async 메서드는 반드시 void, Future, 또는 CompletableFuture 중 하나를 리턴해야 합니다.
  2. Proxy 기반: @Async는 Spring의 프록시를 활용하므로, 같은 클래스 내에서 호출하는 경우 비동기 처리가 동작하지 않습니다.
  3. 예외 처리: 비동기 작업 중 발생한 예외는 별도로 처리해야 합니다.
@Async
public CompletableFuture<String> performTaskWithError() {
    throw new RuntimeException("에러 발생");
}

@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleException(RuntimeException ex) {
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
}

결론: 동기와 비동기의 활용

동기 처리와 비동기 처리는 각각의 장단점이 있으며, 상황에 맞게 선택하여 사용해야 합니다.
동기는 순차적인 작업이 필요할 때 유용하고, 비동기는 대기 시간이 긴 작업이나 병렬 처리가 필요한 경우에 적합합니다.

Spring의 @Async는 비동기 작업을 간단히 구현할 수 있는 강력한 도구입니다.
그러나 적절한 설정과 주의가 필요하며, 병렬 처리에 익숙해질수록 더욱 효과적으로 활용할 수 있습니다.


핵심 요약

  • 동기: 작업 완료 후에 다음 작업 실행.
  • 비동기: 작업 완료를 기다리지 않고 다음 작업 실행.
  • Spring @Async를 사용하면 간단하게 비동기 처리를 구현 가능.
  • @EnableAsync와 Executor 설정이 필요.
  • @Async는 프록시 기반으로 동작하므로, 같은 클래스 내 호출 시 동작하지 않음.
  • 코드 차이점 요약: 동기는 작업의 완료를 기다리는 반면, 비동기는 CompletableFuture를 사용해 작업 완료를 대기하지 않고 다음 작업을 실행.
  • 비동기 처리의 장단점: 처리 속도가 증가하고 시스템 응답성이 향상되지만, 디버깅이 어렵고 스레드 관리 실패 시 성능 저하가 발생할 수 있음.

이 개념은 프로그램의 성능과 응답성을 개선하기 위해 중요합니다.
실무에서도 중요하게 활용되며, 서비스의 연속성에도 많은 영향을 미칩니다.

위 개념을 같이 공부하고, 실무에서도 같이 활용해보면 좋을 것 같습니다.
감사합니다.

profile
포기하기 금지

0개의 댓글