- 여러 스레드를 동시에 실행하여 작업을 ‘병렬로 처리’하는 기술을 의미합니다. 이는 CPU의 활용도를 높이고, 응답 시간을 줄이며, 서버의 처리 능력을 향상합니다.
- 이러한 비동기 작업은 별도의 스레드에서 실행이 되며, 메인 스레드가 블로킹되지 않도록 합니다. 즉, 메인 스레드가 특정 작업을 기다리느라 멈추지 않고, 다른 작업을 계속해서 수행할 수 있습니다.
[ 더 알아보기 ] 💡 병렬 처리
- 여러 작업을 동시에 수행하는 기술을 의미합니다. 이는 여러 개의 프로세서나 스레드를 사용하여 작업을 병렬로 실행함으로써, 작업 처리 시간을 줄이고 시스템 성능을 향상하는 방법입니다. - 주로 대규모 연산 데이터 처리, 시뮬레이션 등에 사용되며, 이를 통해 더욱 빠르고 효율적인 작업처리가 가능합니다.
💡 그럼 일반적으로 사용하는 API 서버는 동기적 처리라고 알고 있는데, 이는 멀티스레드를 사용하지 않는 것일까?
- 일반적인 동기 처리에서도 멀티스레드를 사용할 수 있습니다. 동기 처리는 작업이 완료될 때까지 호출한 메서드가 반환되지 않는 것을 의미하며, 멀티스레드는 여러 스레드를 동시에 실행하여 병렬로 작업을 처리하는 기술입니다. - 따라서 동기 처리에서도 멀티스레드를 활용하여 작업을 병렬로 실행할 수 있습니다. 단, 동기 처리에서는 작업 간의 순서가 보장되며, 비동기 처리와는 다르게 호출한 메서드가 완료될 때까지 기다리는 특성이 있습니다.
- 여러 개의 스레드들을 ‘미리 생성’해 두고 작업 큐에서 작업을 가져와서 실행하는 방식으로, 스레드 생성과 소멸에 따른 오버헤드를 줄이고, 시스템 자원을 효율적으로 활용할 수 있게 합니다.
- 이를 사용하면 스레드 생성 비용을 절감하고, 응답 시간을 단축시키며, 시스템의 안정성을 높일 수 있습니다.
💡 스레드 풀 생성 과정
- 애플리케이션이 시작될 때, 사전에 미리 지정한 스레드의 개수에 따라서 스레드 풀 내에 스레드가 생성이 됩니다.
💡 스레드 풀 처리 과정 - 생성된 스레드를 기반으로 이를 이용한 처리과정을 이해합니다
1. 애플리케이션(Application)
- 비동기 처리의 하나의 새로운 작업(New Task)을 발생시켰고, 이를 작업 큐로 제출(Submit)을 하게 됩니다.
2. 작업 큐(Task Queue)
- 전달받은 작업(Task)은 큐에서 스레드의 사용이 가능할 때까지 보관을 합니다. - 또한 큐에서는 FIFO 형태로 먼저 들어온 데이터가 먼저 처리하는 구조를 가지며 순차적으로 데이터가 쌓이고 들어온 순서대로 처리가 수행됩니다.
3. 스레드(Thread)
- 스레드 풀에 있는 스레드 중 하나가 사용이 가능해지면, 작업 큐에서 작업을 가져와서 실행을 하게 됩니다. - 작업이 완료되면, 스레드는 유휴 상태가 되며, 작업이 발생하면 다시 큐에서 다음 작업을 가져와서 처리를 합니다.
💡 Java 내에서 스레드 풀 생성 및 처리 과정 1. 스레드 풀 생성 : ExecutorService
- 스레드 풀을 생성할 때, 미리 정해진 수의 스레드가 생성됩니다. Java에서는 ExecutorService 인터페이스를 통해 스레드 풀을 생성할 수 있습니다
2. 작업 제출 : submit()
- 스레드 풀에 작업을 제출하면, 작업은 작업 큐에 추가됩니다. 큐에 추가된 작업은 스레드 풀 내의 스레드에 의해 실행됩니다.
3. 작업 실행
- 스레드 풀 내의 스레드가 작업 큐에서 작업을 가져와 실행합니다. 스레드가 유휴 상태일 때, 다음 작업을 가져와 실행합니다.
4. 작업 완료 - 각 스레드는 작업을 완료하면 다시 유휴 상태로 돌아가며, 다음 작업을 대기합니다.
5. 스레드 풀 종료 : shutdown()
- 더 이상 작업을 제출하지 않을 때, 스레드 풀을 종료해야 합니다. 이를 위해 shutdown() 또는 shutdownNow() 메서드를 호출합니다. - shutdown()은 이미 제출된 작업을 완료한 후 스레드 풀을 종료하고, shutdownNow()는 실행 중인 작업을 중단하고 즉시 종료합니다.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 스레드 풀 생성
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 작업 제출
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {
System.out.println("Thread Name: " + Thread.currentThread().getName());
});
}
// 스레드 풀 종료
executorService.shutdown();
}
}
💡 스레드 풀 생성 및 처리 과정 결과
[ 더 알아보기 ] 💡 스레드 풀을 사용하지 않으면 어떻게 될까?
- 스레드 풀을 사용하지 않으면, 각 작업이 요청될 때마다 새로운 스레드가 생성되고 작업이 완료되면 해당 스레드가 소멸됩니다. - 이는 스레드 생성과 소멸에 따른 오버헤드가 발생하여 시스템 자원의 비효율적인 사용을 초래할 수 있습니다. - 또한, 스레드 수가 무제한으로 증가할 수 있어 시스템 성능이 저하되거나 심지어 시스템이 다운될 위험이 있습니다.
💡 스레드 풀이랑 커넥션 풀이랑 비슷한 개념인 거 같은데?
- 스레드 풀과 커넥션 풀은 개념적으로 유사합니다. 두 경우 모두 자원을 미리 할당하여 필요할 때 빠르게 사용할 수 있도록 하여, 자원 생성 및 소멸에 따른 오버헤드를 줄이고 시스템 성능을 향상하는 역할을 합니다. - 스레드 풀은 스레드를 미리 생성해 두고 작업을 처리하며, 커넥션 풀은 데이터베이스 연결을 미리 생성해 두고 필요할 때 재사용합니다.
💡 [참고] Connection Pool을 이용한 DBCP에 대한 이해가 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
- 주로 @Configuration을 선언한 클래스에서 사용됩니다. 해당 어노테이션을 선언한 클래스 내에서는 메서드 단위로 비동기 처리 활성화 설정을 하거나 @Bean 어노테이션과 함께 Executor의 스레드 풀 설정이나 비동기 작업에 대한 설정을 정의하는 데 사용이 됩니다.
1.1. @EnableAsync 어노테이션 속성
💡 @EnableAsync 어노테이션 속성
속성 명
리턴 타입
default
설명
annotation
Class<? extends Annotation>
클래스 또는 메소드 수준에서 감지할 'async' 어노테이션 타입을 나타냅니다.
mode
AdviceMode
AdviceMode.PROXY
비동기 어드바이스가 적용될 방식을 나타냅니다. AdviceMode.PROXY AdviceMode.ASPECTJ을 제공합니다.
order
int
-
AsyncAnnotationBeanPostProcessor가 적용될 순서를 나타냅니다.
proxyTargetClass
boolean
false
표준 Java 인터페이스 기반 프록시 대신 서브클래스 기반(CGLIB) 프록시가 생성될지 여부를 나타냅니다.
- @Configuration 어노테이션과 함께 @EnableAsync 어노테이션은 사용이 됩니다. - @Bean 어노테이션 내에 Executor 이름을 지정하고 이에 대한 속성을 재정의합니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}
2. @Async 어노테이션
💡 @Async 어노테이션
- 메서드를 ‘비동기 메서드’로 실행하도록 지정하는 어노테이션입니다. 즉, 메서드 호출은 즉시 반환되고, 실제 작업은 별도의 스레드에서 비동기적으로 수행이 됩니다.
- 해당 어노테이션이 지정된 메서드는 반드시 접근 제한자로 public을 선언해야 합니다. 이는 메서드가 public 이어야 프록시 될 수 있기 때문입니다.
[ 더 알아보기] 💡 프록시 될 수 있다는 말은 뭘까?
- 메서드가 호출될 때, 실제 메서드 호출을 가로채서 별도의 로직을 추가하거나 대체할 수 있는 프록시 객체가 사용된다는 의미입니다. - Spring에서는 주로 AOP(Aspect-Oriented Programming)를 통해 이런 프록시 패턴을 사용하여 비동기 처리, 트랜잭션 관리, 로깅 등의 기능을 구현합니다.
- 기본적으로 @Async 어노테이션으로 사용하면 SimpleAsyncTaskExecutor로 실행이 되며, 이름으로 지정한 taskExecutor는 AsyncConfig 내에서 재정의한 Executor의 이름으로 매핑이 됩니다.
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class MyAsyncService {
@Async("taskExecutor")
public void asyncMethod() {
// 비동기 작업 수행
}
}
3) 비동기 반환 유형(Async Return Type) : 반환 유형이 존재하지 않는 경우
1. 비동기 반환 유형이 존재하면 동기 처리 방식과 같은 것이지 않을까?
💡 비동기 반환 유형이 존재하면 동기 처리 방식이지 않을까?
- 결과적으로, 반환 유형이 존재한다고 해서 동기 처리방식과 같은 것은 아닙니다.
- 동기 처리 방식의 경우, ‘호출되는 작업’이 완료가 될 때까지 블로킹이 됩니다. 즉, 호출한 메서드는 작업이 완료될 때까지 기다려야 하며, 그동안 다른 작업이 수행할 수 없습니다.
- 비동기 처리 방식의 경우, ‘호출되는 작업’이 즉시 반환되며 작업은 별도의 스레드에서 비동기적으로 실행됩니다. 즉, 호출한 메서드는 작업이 완료될 때까지 블로킹되지 않으며, 작업이 완료된 후 콜백 또는 Future 객체를 통해 결과를 받을 수 있습니다.
1.1. 동기(Synchronous) 처리 과정
💡 동기 처리 과정
- 해당 과정에서는 '메인 스레드'는 블로킹이 되며, 5초간의 중지가 되는 동안을 기다린 뒤 수행하는 동기 처리가 수행이 됩니다.
- 해당 synchronousMethod() 메서드를 호출하게 되면, 작업 중인 스레드의 이름이 콘솔로 출력이 되고, Thread.sleep(5000)를 통해서 5초간 스레드가 중지되어 블로킹이 됩니다. 5초가 지난 후에 리턴 값을 반환하고 종료됩니다.
public String synchronousMethod() {
// 1. 현재 스레드의 이름을 출력합니다.
System.out.println("Execute method synchronously " + Thread.currentThread().getName());
try {
// 2. 스레드를 5초간 일시 중지 시킵니다.
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "Error: " + e.getMessage();
}
// 3. 작업이 완료된 후 결과를 반환합니다.
return "hello world !!!!";
}
💡 동기 처리 과정 결과
- 아래의 처리 과정의 결과에서는 콘솔로 출력되는 Execute method synchronously 부분이 수행되고, 멈춘 상태가 같은 대기 상태로 이어지다 결과값인 "hello world!!!!"가 출력됨을 확인할 수 있습니다.
💡 [참고] 아래와 같은 호출을 하였을 경우, 비동기 처리로 수행되는 코드에 대해서도 응답 값을 받아서 동기와 같이 처리가 됨을 확인할 수도 있습니다. (* 해당 내용은 뒤에 내용에서 이어집니다)
/**
* 비동기 호출 수행 테스트
*/
@Test
void asyncMethod() {
// 비동기 메서드 호출
CompletableFuture<String> future = syncAsyncDiffService.asyncMethod();
// 다른 작업을 수행
System.out.println("Doing other tasks...");
try {
// 비동기 작업의 결과를 기다림
String result = future.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
2. 반환 유형이 존재하지 않는 경우 : void
💡 반환 유형이 존재하지 않는 경우 : void
- @Async 어노테이션을 사용하는 메서드 내에서 비동기적으로 실행되며 호출자는 즉시 반환이 됩니다. 이때 메서드는 처리 결과를 반환하지 않습니다. - 반환 유형이 존재하지 않는 경우는 단순 작업이나 I/O 작업에서 주로 사용이 됩니다. - 예를 들어, 로그 기록이나 알림 전송과 같은 작업들이 이에 해당합니다.
💡 반환 유형이 존재하지 않는 경우 사용예시
- @Async 어노테이션을 통해서 메서드 호출이 비동기적으로 처리되며, 메서드의 리턴타입이 void로 반환되지 않으며 비동기 처리가 되는 메서드를 의미합니다. - 해당 메서드 내에서는 별도의 리턴값을 받지 않고 처리가 됩니다
/**
* [Async] 반환 유형이 존재하지 않는 경우 : void
*/
@Async
@Override
public void asyncVoidType() {
System.out.println("Execute method asynchronously. :: " + Thread.currentThread().getName());
}
4) 비동기 반환 유형(Async Return Type) : 반환 유형이 존재하는 경우
1. Future, ListenableFuture, CompletableFuture 요약
💡 Future, ListenableFuture, CompletableFuture 요약
- 해당 경우에서는 비동기 처리가 수행된 이후에 이에 따르는 응답값을 전달받습니다.
클래스
설명
주요 기능
단점
Future
비동기 작업의 결과를 나타내는 객체입니다.
get 메서드를 통해 결과를 얻을 수 있습니다. 작업 완료 전까지 get 호출 시 블로킹됩니다.
get 메서드 호출 시 블로킹될 수 있으며, 콜백 기능이 없습니다.
ListenableFuture
Future의 확장으로, 비동기 작업 완료 시 콜백을 등록할 수 있는 기능을 제공합니다.
콜백을 통해 비동기 작업 완료 시 특정 작업을 수행할 수 있으며, 효율적인 비동기 프로그래밍이 가능합니다.
콜백 등록 시 주의가 필요하며, Future 객체의 다른 제한사항도 동일하게 적용됩니다.
CompletableFuture
Future의 확장으로, 복잡한 비동기 흐름 처리를 위한 다양한 유틸리티 메서드를 제공합니다.
콜백을 통해 비동기 작업 완료 시 특정 작업을 수행할 수 있으며, 여러 비동기 작업을 조합하거나 체이닝할 수 있습니다.
복잡한 비동기 작업의 경우 코드 가독성이 떨어질 수 있으며, 예외 처리를 적절히 다뤄야 합니다.
[ 더 알아보기 ] 💡 블로킹이 된다는 말은 무슨 말일까?
- 특정 작업이 완료될 때까지 해당 스레드가 멈추거나 대기 상태가 되는 것을 의미합니다. - 예를 들어, 어떤 메서드에서 블로킹 호출을 하면 그 메서드가 반환되기 전까지 다른 작업을 수행할 수 없습니다. 따라서 응답 시간이 길어지거나 성능이 저하될 수 있습니다.
2. Future
💡 Future
- Java의 비동기 프로그래밍에서 사용되는 인터페이스로 비동기 작업의 결과를 나타내는 클래스입니다. 이는 작업이 완료되었는지 여부를 확인하고, 완료된 작업의 결과를 가져오거나, 작업이 완료될 때까지 기다리는 등의 기능을 제공합니다.
- Future 객체의 get 메서드를 호출하면 비동기 작업이 완료될 때까지 블로킹됩니다. 따라서 get 메서드를 호출할 때는 주의가 필요합니다. - 주로 결과를 필요로 하는 복잡한 작업이나 시간이 오래 걸리는 작업에 사용됩니다. - 예를 들어, 데이터베이스 쿼리나 파일 처리 작업 등이 이에 해당합니다.
- 테스트를 5번 반복한 결과입니다. 최초 Async가 실행이 되고 결과 값이 반환이 되었습니다.
3. ListenableFuture
💡 ListenableFuture
- Future의 확장으로 비동기 작업의 완료를 기다리는 동안 '콜백'을 등록하여 작업이 완료될 때 특정 동작을 실행할 수 있는 기능을 제공합니다.
- 해당 객체를 통해 비동기 작업이 완료되었을 때, 특정 작업을 수행할 수 있도록 콜백을 설정할 수 있습니다. 이는 비동기 작업의 완료를 기다리지 않고도 후속 작업을 설정할 수 있어서 효율적인 비동기 프로그래밍을 가능하게 합니다. - Spring Framework 6.x 이상 버전에서 해당 클래스는 Deprecated 되었습니다. - 주로 결과를 필요로 하는 복잡한 작업이나 시간이 오래 걸리는 작업에 사용됩니다.
[ 더 알아보기 ]
💡 콜백이란 무엇일까?
- 콜백은 특정 작업이 완료된 후 자동으로 호출되는 함수 또는 메서드를 의미합니다. 비동기 작업은 메인 스레드와 별도로 실행되기 때문에, 작업이 완료되었을 때 어떤 작업을 수행해야 하는지 지정할 필요가 있습니다. 이때 콜백 메서드를 사용하여 작업 완료 후의 처리될 내용에 대해서 정의합니다. - 주로 비동기 프로그래밍에서 사용되며, 비동기 작업이 완료된 후 후속 작업을 수행하는 데 유용합니다.
3.1. ListenableFuture 메서드
💡 ListenableFuture 메서드
- 해당 객체는 interface java.util.concurrent.Future로부터의 데이터를 상속받아서 모두 이용이 가능하면서 ListenableFuture 객체만의 메서드 이용이 가능합니다. - 해당 메서드는 모두 Spring 6.x 버전에서는 Deprecated 되었고 CompletableFuture를 사용하기를 권장하고 있습니다.
메서드
리턴 타입
설명
addCallback(ListenableFutureCallback<? super T> callback)
void
비동기 작업이 완료되었을 때 실행될 콜백을 등록합니다.
addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback)
💡 ListenableFuture 사용 예시 - asyncListenableFuture() 메서드를 구성하였습니다. 해당 메서드는 @Async를 통해서 비동기 처리를 수행하는 메서드로 선 언하였고, ListenableFuture <String> 객체의 리턴타입을 가진 형태입니다.
1. 현재 스레드의 이름을 출력합니다. - 스레드가 생성되고 반환되는 과정을 확인하기 위해 로그를 작성하였습니다.
2. 스레드를 5초간 일시 중지 시킵니다. - 비동기 메서드의 실행 중에 일정 시간을 기다리게 하기 위해서입니다. 5초 동안 스레드를 일시 중지하고 그 후 비동기 작업의 결과를 반환합니다. 3. 비동기 작업의 결과를 반환합니다. - ListenableFuture <String> 리턴타입에 맞는 형태로 객체를 생성하였습니다. 4. 콜백을 등록합니다. - addCallback() 메서드를 통해서 성공 시, 실패 시에 대한 콜백함수를 받습니다. 각각 성공에 따르는 로그와 실패에 따르는 로그를 작성하였습니다.
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.util.concurrent.ListenableFuture;
// @Async 어노테이션으로 비동기 메서드를 정의합니다.
@Async
@Override
public ListenableFuture<String> asyncListenableFuture() {
// 1. 현재 스레드의 이름을 출력합니다.
System.out.println("Execute method asynchronously - " + Thread.currentThread().getName());
try {
// 2. 스레드를 5초간 일시 중지 시킵니다.
Thread.sleep(5000);
// 3. 비동기 작업의 결과를 반환합니다.
ListenableFuture<String> future = new AsyncResult<>("hello world !!!!");
// 4. 콜백을 등록합니다.
future.addCallback(new ListenableFutureCallback<String>() {
@Override
public void onSuccess(String result) {
// 비동기 작업이 성공적으로 완료되었을 때 실행되는 콜백
System.out.println("Success with result: " + result);
}
@Override
public void onFailure(Throwable t) {
// 비동기 작업이 실패했을 때 실행되는 콜백
System.out.println("Failure: " + t.getMessage());
}
});
return future;
} catch (InterruptedException e) {
System.out.println("error :: " + e.getMessage());
}
return null;
}
💡 [참고] 'org.springframework.util.concurrent.ListenableFuture' is deprecated since version 6.0
- Spring 버전 6.0부터 이 클래스는 더 이상 사용되지 않도록(deprecated) 표시되었습니다. - 이는 해당 클래스가 더 이상 권장되지 않으며, 향후 버전에서는 제거될 수 있음을 의미합니다. 대신 CompletableFuture와 같은 대체 클래스를 사용하는 것이 권장됩니다. CompletableFuture는 Java 8에서 도입되었으며, 비동기 프로그래밍을 더 쉽고 유연하게 할 수 있는 다양한 기능을 제공합니다.
4. CompletableFuture
💡 반환 유형이 존재하는 경우 : CompletableFuture
- Java 8에서 도입된 Future의 구현체이자 확장 기능을 제공하는 클래스입니다. 이전 Future 보다 비동기 작업을 더 쉽게 작성하고 관리할 수 있도록 도와줍니다.
- 주요 기능으로는 작업을 체인 방식으로 연결할 수 있는 thenApply, thenAccept, thenCompose 등의 메서드와, 작업 완료 후 특정 동작을 실행할 수 있는 whenComplete, handle 등의 메서드가 있습니다. - 또한, CompletableFuture는 명시적으로 완료 상태로 설정할 수 있어, 외부에서 작업의 성공 또는 실패를 제어할 수 있습니다. - 주로 비동기 작업의 결과를 조합하거나, 체이닝 하여 연속적인 비동기 작업을 수행할 때 사용됩니다. 예를 들어, 여러 비동기 작업을 순차적으로 실행하거나 결과를 조합하는 데 유용합니다.
💡 CompletableFuture 사용 예시 -2 - 해당 예시에서는 콜백으로 성공/실패에 대한 후처리가 포함된 예시입니다. 1. CompletableFuture 객체를 생성하고, 비동기 작업을 수행하여 결과값을 반환받습니다. 2. 비동기 작업이 완료되었을 때 수행할 동작을 정의합니다. 3. 비동기 작업의 성공과 실패에 따라 다르게 처리합니다.
4. 메인 스레드가 종료되지 않도록 2초간 대기합니다.
5. 결과적으로 CompletableFuture 객체를 반환합니다.
@Override
public CompletableFuture<String> asyncCompletableFuture2() {
// 1. CompletableFuture 객체를 생성하고, 비동기 작업을 수행하여 결과값을 반환받습니다.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
// 2. 비동기 작업이 완료되었을 때 수행할 동작을 정의합니다.
future.whenComplete((result, exception) -> {
if (exception == null) {
// 3. 비동기 작업이 성공적으로 완료되었을 때 실행되는 블록
System.out.println("Completed successfully with result: " + result);
} else {
// 4. 비동기 작업이 실패했을 때 실행되는 블록
System.out.println("Completed with error: " + exception.getMessage());
}
});
// 5. 메인 스레드가 종료되지 않도록 2초간 대기합니다.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("error :: " + e.getMessage());
}
// 6. CompletableFuture 객체를 반환합니다.
return future;
}
- 비동기 작업이 완료될 때까지 메인 스레드가 살아있도록 하기 위해서입니다. 만약 메인 스레드가 먼저 종료되면, 비동기 작업이 완료되기 전에 프로그램이 종료될 수 있습니다. - 따라서 비동기 작업이 완료되는 것을 보장하기 위해 메인 스레드를 일시적으로 대기 상태로 만들어 주는 것입니다.
💡 그러면 메인 스레드는 항상 대기하도록 sleep()을 해주어야 하는가?
- 그렇지 않습니다. 일반적으로는 비동기 작업이 완료될 때까지 메인 스레드가 대기할 필요는 없습니다. - 비동기 작업이 완료되면, 해당 작업의 콜백 또는 후속 작업이 자동으로 실행됩니다. - 다만, 예제 코드에서처럼 프로그램이 종료되지 않도록 하기 위해 일시적으로 대기 상태로 만들어 주는 경우가 있을 수 있습니다. 하지만 실제 애플리케이션에서는 비동기 작업을 처리하는 다른 방법을 사용할 수 있습니다.
💡Thread.currentThread(). getName()로 조회되는 것은 메인 스레드인가?
- Thread.currentThread().getName()로 조회되는 것은 메인 스레드가 아닙니다. - @Async 어노테이션을 사용하여 비동기 메서드를 정의하면, 해당 메서드는 별도의 스레드에서 실행됩니다. - 따라서, Thread.currentThread().getName()를 호출하면 현재 실행 중인 비동기 스레드의 이름이 반환됩니다.
💡 [참고] 해당 글에서 테스트한 코드는 아래의 Repository 내에서 확인이 가능합니다