왜 이걸 알아야 할까?

개발 중에 이런 상황을 겪어볼 수 있다.

  • 스레드가 고갈돼서 서버가 멈춘다.
  • @Async 붙였는데 오히려 느려졌다.
  • WebClient 썼는데 로그 순서가 뒤죽박죽이다.
  • ThreadLocal 데이터가 갑자기 사라진다.

이 문제들의 공통점은 "스레드" 이다.

 

내가 만든 코드가 실제로 어떤 스레드에서 돌아가고 있는지 모른다면, 성능파악과 튜닝도 어려울것이다.

 

그래서 HTTP 요청 하나당 실제로 몇 개의 스레드가 작동하는지 환경별로 한번 알아보자.

 

환경 설명
Spring MVC 기본 동기 처리(Tomcat)
Spring MVC + @Async 일부 작업 비동기 분리
Spring MVC + WebClient 외부 API 호출 시 비동기
WebFlux + Netty 전체 논블로킹, 이벤트 기반

 

 

요청 하나 = 스레드 하나?는 옛말?

보통 아래와 같이 생각한다.

 

이 구조는 맞다 그런데 여기에 다음이 붙는 순간 스레드가 복제된다.

  • @Async 사용
  • WebClient 비동기 콜백
  • 로깅 비동기 처리
  • Netty 기반 이벤트 루프

각 케이스를 로그와 함께 한번 알아보자.

 

 

1. [Spring MVC] 동기 방식

@RestController
public class TestController {

    @GetMapping("/sync")
    public String sync() {
        log.info("스레드: {}", Thread.currentThread().getName());
        return "ok";
    }
}

 

실행 결과 

 

  • Controller → Service → Repository → 응답까지 모두 하나의 워커 스레드(exec-4) 에서 작동한다.
  • 요청 하나 = 스레드 하나 (정확)

 

 

2. [Spring MVC + @Async] - 작업을 나눴더니 스레드가 늘어났다.

@Service
public class MyService {
    @Async
    public void doAsync() {
        log.info("비동기 스레드: {}", Thread.currentThread().getName());
    }
}

@GetMapping("/async")
public String async() {
    log.info("요청 스레드: {}", Thread.currentThread().getName());
    myService.doAsync();
    return "ok";
}

 

실행 결과

 

  • 요청은 exec-1 이 처리했다.
  • 내부 로직은 별도 스레드(task-1)로 넘어갔다.
  • 요청은 하나지만 2개의 스레드가 동작했다.

 

 

3. [Spring MVC + WebClient] - 응답을 기다리는 동안 다른 스레드가 개입

@GetMapping("/webclient")
public Mono<String> callApi() {
    log.info("요청 시작: {}", Thread.currentThread().getName());

    return webClient.get()
        .uri("http://localhost:8081/slow")
        .retrieve()
        .bodyToMono(String.class)
        .doOnNext(response -> log.info("응답 받은 스레드: {}", Thread.currentThread().getName()));
}

 

실행 결과 (Spring WebClinet + 비동기)

  • 시작은 exec-2 에서 진행했다.
  • WebClient는 내부적으로 다른 스레드 풀에서 응답을 처리했다.
  • 요청 하나에 최소 2개 이상의 스레드가 동작했다.

 

 

4. [Spring WebFlux + Netty] - 더 이상 스레드 흐름은 예측 불가

@GetMapping("/webflux")
public Mono<String> reactive() {
    log.info("요청 시작: {}", Thread.currentThread().getName());

    return Mono.just("응답")
        .delayElement(Duration.ofMillis(500))
        .doOnNext(data -> log.info("응답 처리 스레드: {}", Thread.currentThread().getName()));
}

 

실행 결과

  • 요청은 reactor-http-nio-* 에서 수신했다.
  • 응답은 완전히 다른 워커 스레드에서 처리했다 (parallel-*)
  • 1 요청에 최소 2개 이상 스레드, 심지어 바뀔 수도 있다.

 

정리해보자면

환경 요청당 스레드 수 특징
Spring MVC 1 예측 가능, 단순
MVC + @Async 2+ 스레드 분기
MVC + WebClient 2+ 응답 처리 별도
WebFlux 2 ~ n 스레드 다변화, 예측 어려움

 

스레드가 바뀌면 ThreadLocl에 저장한 값은 사라지고, 

로깅, 트랜잭션, MDC, 세션 등 "스레드 고정이 전제된 기능"이 깨질 수 있다.

'Back-End > Java' 카테고리의 다른 글

할머니도 이해할 수 있는 자바 Thread  (0) 2025.05.01
try-catch  (0) 2024.07.06
스레드  (0) 2024.07.05
프로세스  (0) 2024.07.04
Java HTTP 통신  (1) 2024.06.12

+ Recent posts