
안녕하세요.
이번 시간에는 동기(Synchronous)와 비동기(Asynchronous)의 개념에 대해서 공부해보겠습니다.
이 두 가지는 작업의 실행 흐름과 처리 방식을 결정하는 핵심 요소로, 애플리케이션의 성능과 사용자 경험에 큰 영향을 미칩니다.
이번 포스팅에서는 동기와 비동기의 정의와 차이점부터, Spring에서 제공하는 @Async 어노테이션의 활용법까지 공부해보겠습니다.
Ex) 전화 통화: 상대방이 응답할 때까지 기다려야 대화가 가능합니다.
Ex) 문자 메시지: 메시지를 보내고 즉시 다른 작업을 수행할 수 있으며, 상대방의 답장을 기다리지 않아도 됩니다.
| 특징 | 동기(Synchronous) | 비동기(Asynchronous) |
|---|---|---|
| 작업 흐름 | 순차적으로 실행 | 병렬적으로 실행 |
| 대기 시간 | 작업 완료까지 대기 | 대기하지 않음 |
| 성능 | 오래 걸리는 작업 시 병목 발생 가능 | 병렬 처리로 효율성 증가 |
| 사용 사례 | 데이터베이스 트랜잭션, 간단한 연산 | 파일 처리, 외부 API 호출, 비동기 이벤트 |
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에서는 CompletableFuture와 같은 API를 사용하여 비동기 작업을 구현할 수 있습니다. 비동기는 작업의 결과를 기다리지 않고 다음 작업을 실행합니다.
public void asyncExample() {
System.out.println("첫 번째 작업 시작");
CompletableFuture.runAsync(() -> performTask());
System.out.println("두 번째 작업 시작");
}
실행 결과
첫 번째 작업 시작 두 번째 작업 시작 (2초 후) 작업 완료
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;
}
}
비동기 메서드 정의:
@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초 후, "작업 완료"가 출력됩니다.
void, Future, 또는 CompletableFuture 중 하나를 리턴해야 합니다.@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는 비동기 작업을 간단히 구현할 수 있는 강력한 도구입니다.
그러나 적절한 설정과 주의가 필요하며, 병렬 처리에 익숙해질수록 더욱 효과적으로 활용할 수 있습니다.
CompletableFuture를 사용해 작업 완료를 대기하지 않고 다음 작업을 실행.이 개념은 프로그램의 성능과 응답성을 개선하기 위해 중요합니다.
실무에서도 중요하게 활용되며, 서비스의 연속성에도 많은 영향을 미칩니다.
위 개념을 같이 공부하고, 실무에서도 같이 활용해보면 좋을 것 같습니다.
감사합니다.