https://velog.io/@iknow/RestTemplate과-Thread-State를 작성하고 난 이후, 스터디원과 디스커션을 하다가 Nonblocking방식으로 동작하는 webclient의 경우에는 어떻게 동작할 지 궁금증이 들었다.
위의 링크된 글과 이어지는 내용이니 한 번 읽고 오는 것을 추천한다.
그래서 바로 다음과 같이 이전 코드를 수정하고 같은 로직을 수행해봤다.
@GetMapping("/test")
public void getResponse() throws InterruptedException {
Thread.currentThread().setName("sender Thread");
Thread requestThread = new Thread(()-> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
WebClient webClient = WebClient.create("http://localhost:8080");
log.info("requsest send");
Mono<Map> response = webClient.get().uri("/web-client-test/request").retrieve().bodyToMono(Map.class);
response.subscribe(responseMap -> {
log.info("response receive");
log.info(Thread.currentThread().getState().toString());
log.info(responseMap.toString());
});
log.info(Thread.currentThread().getName() + " terminated");
});
requestThread.setName("requestThread");
requestThread.start();
for (int i=0; i<15; i++) {
log.info("reqeustThread state: " + requestThread.getState());
Thread.sleep(300);
}
}
참고로 위의 “http://localhost:8080/web-client-test/request” 의 GET요청은 2초간 Thread sleep이후 결과를 반환하는 메서드이다.
또한 이번에는 로그를 출력하는 스레드까지 확실히 파악하기 위해 Lombok 의존성을 추가하고 Slf4j를 사용하여 sout이 아닌 로그를 출력해 보았다.
이번 결과는 위와 같이 출력되었다.
사실 원래 내가 예상했던것은 request send를 출력한 이후 이번에야 말로 requestThread가 WAITING or BLOCKED상태가 될 것이라고 예상했었는데, 이번에는 TERMINATED상태로 변경되었다. 스레드가 종료되었다는 뜻이다.
내가 예상한 대로라면 requestThread가 요청을 보낸 이후 waiting, blocked이다가 response가 도착하면 다시 runnable로 바뀐다고 착각했었는데, 위에서 확인했다 싶이 Non-Blocking은 그렇게 동작하는 것이 아니라 reqeustThread는 non-blocking 요청 이후 그대로 코드를 진행하여 마지막에 terminated되고, response가 도착한 이후에는 콜백 함수를 실행하기 위한 ctor-http-nio-2라는 이름의 새로운 스레드가 생성되고 로그를 작성하는 것이다.
NonBlocking 특성상 request이후에도 코드를 진행할 수 있다는 점. 코드 진행을 끝내면 스레드가 종료된다는 점을 생각할 수 있는 코드였다.
다음으로는 webclient를 사용해서 blocking 요청을 사용해 보았다. 코드와
@GetMapping("/test")
public void getResponse() throws InterruptedException {
Thread.currentThread().setName("test Thread");
Thread requestThread = new Thread(()-> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
WebClient webClient = WebClient.create("http://localhost:8080");
log.info("requsest send");
Mono<Map> response = webClient.get().uri("/web-client-test/request").retrieve().bodyToMono(Map.class);
// Disposable d = response.subscribe(responseMap -> {
// log.info("response receive");
// log.info(Thread.currentThread().getState().toString());
// log.info(responseMap.toString());
// });
log.info(response.block().toString()); //blocking 요청
log.info(Thread.currentThread().getName() + " terminated");
});
requestThread.setName("requestThread");
requestThread.start();
for (int i=0; i<15; i++) {
log.info("reqeustThread state: " + requestThread.getState());
Thread.sleep(300);
}
}
이전에 RestTemplate을 사용했을 때와는 달리 요청을 보낸 이후 WAITING상태로 간 것을 확인 할 수 있다.
이 결과가 예전 RestTemplate와 다른 결과인 점은 요청을 보낸 이후 RUNNABLE 상태가 아닌 WAITING 상태라는 점이다. 기본적으로 webclient는 비동기 요청을 하기 때문에 Blocking 요청 또한 Asyc-Blocking 요청을 보낸다
사실 마지막으로 해보고 싶었던 것은 sync-Non Blocking 을 구현 할 수 있을까 고민하고 시도해봤었는데, webclient는 기본적으로 async요청을 하기 때문에 힘들다는 것을 확인 할 수 있었다.
다만, sync-NonBlocking인것 “처럼” 코드를 작성해 보았는데
@GetMapping("/test")
public void getResponse() throws InterruptedException {
Thread.currentThread().setName("test Thread");
Thread requestThread = new Thread(()-> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
WebClient webClient = WebClient.create("http://localhost:8080");
log.info("requsest send");
Mono<Map> response = webClient.get().uri("/web-client-test/request").retrieve().bodyToMono(Map.class);
AtomicReference<Object> a = new AtomicReference<>();
Disposable d = response.subscribe(r -> a.set(r));
while (!d.isDisposed()) {
try {
Thread.sleep(300);
log.info("response not received.");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.info(a.toString());
log.info(Thread.currentThread().getName() + " terminated");
});
requestThread.setName("requestThread");
requestThread.start();
for (int i=0; i<15; i++) {
log.info("reqeustThread state: " + requestThread.getState());
Thread.sleep(300);
}
}
async-Non Blocking 요청이지만, 스레드간 공유할 수 있는 객체인 AtomicReference에 객체 a를 선언하고 Consumer내에서 응답을 a에 할당하는 로직을 담아 요청을 보내 마치 sync-NonBlocking인것 “처럼” 진행 할 수 있다.