이 전에 Thread에 대한 개념과 간단한 사용법을 포스팅을 했었습니다. 이 번에는 좀 더 디테일하게 자바에서 제공하는 비동기에 대해서 학습한 내용과 예시 코드를 정리해보겠습니다.
간단하게 스레드를 생성하고 실행하는 클래스입니다. Runnable, Task를 상속받은 클래스를 사용해도 되고, 익명 객체 + 람다 형식으로도 사용할 수 있습니다.
해당 스레드를 종료하기 위해서는 interrupt() 함수를 사용하면 됩니다. interrupt() 메소드는 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할을 합니다. 이것을 이용하면 Thread의 run() 메소드를 정상 종료시킬 수 있습니다.
public static void main(String[] args) throws IOException {
SpringApplication.run(DemoApplication.class, args);
Random random = new Random();
for(int i = 0; i < 10 ; i++){
new Thread(() -> print(random.nextInt(100))).start();
}
}
public static int print(int value){
log.info("number = {}", value);
return value;
}
실행 결과
Thread의 경우에는 작업이 있을 때마다 생성하고 작업이 끝나면 종료하는 추가적인 작업과 자원이 필요합니다. 그래서 좀 더 쉽고 효율적으로 스레드를 사용하기 위해서 ExecutorService가 있습니다.
ExecutorService의 동작원리를 그림으로 표현하면 다음과 같습니다. 스레드를 스레드풀로 관리를 하고 해야할 작업을 큐로 관리를 하는 형식입니다.
public static void main(String[] args) throws IOException {
SpringApplication.run(DemoApplication.class, args);
final ExecutorService executorService = Executors.newFixedThreadPool(10);
Random random = new Random();
for(int i = 0; i < 10 ; i++){
executorService.submit(() -> print(random.nextInt(100)));
}
}
public static int print(int value){
log.info("number = {}", value);
return value;
}
newFixedThreadPool
newCachedThreadPool
실행 결과
Thread에서는 Thread-1, Thread-2, ... Thread-N 방식이었다면 ExecutorService에서는 pool-2-thread-1, pool-2-thread-2, ... pool-2-thread-N 방식을 확인할 수 있습니다.
다음으로는 Future입니다. 지금까지 스레드는 비동기로 실행한 후에 결과값을 사용하고 있지 않습니다.
Future에서는 get() 함수를 사용하면 해당 작업의 리턴값을 얻을 수 있습니다. 하지만 get()을 사용한 순간부터 결과값을 위해 작업을 기다리기 때문에 비동기의 의미가 사라집니다.
Future의 문제를 해결하기 위해 등장한 것이 CompletableFuture입니다. CompletableFuture으로 콜백을 설정하여 비동기 작업의 결과물을 다시 한번 해당 스레드로 비동기 작업을 호출할 수 있습니다.
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(DemoApplication.class, args);
final ExecutorService executorService = Executors.newFixedThreadPool(10);
Random random = new Random();
for(int i = 0; i < 5 ; i++){
CompletableFuture.supplyAsync(
() -> print(random.nextInt(100)), executorService)
.thenAccept(t -> log.info("number = {}", t));
}
Thread.sleep(3000);
}
public static int print(int value){
log.info("number = {}", value);
return value;
}
실행 결과
비동기 작업을 실행한 스레드가 재차 비동기로 결과값을 가지고 실행한 것을 확인할 수 있습니다.
CompletableFuture은 이 외에도 두 가지 비동기 작업의 결과값을 사용하는 콜백(thenCombine() 함수)도 설정할 수 있으며, 연속 콜백(thenCompose() 함수)을 설정할 수 도 있습니다.