Spring Boot는 내부적으로 톰캣같은 웹 서버를 내장하고 있고, 이 톰캣은 Thread Pool을 이용해 요청마다 새로운 thread에서 처리하도록 되어 있다.
클라이언트 요청 -> 톰캣 -> thread pool에서 스레드 하나 꺼냄 -> 컨트롤러 메서드 실행
컨트롤러에서 만든 메서드는 이미 별도의 요청 처리용 스레드에서 실행 된다.
이 스레드는 보통 http-nio-8080-exec-1 같은 이름을 가진다.
public class Worker implements Runnable {
@Override
public void run() {
// 백그라운드 작업 수행
}
}
직접 Thread, Runnable, ExecutorService 등을 이용해서 만든 스레드는 Spring의 요청/응답 처리 흐름과는 별개로 동작하는 수동 생성 백그라운드 작업이다.
예시)
1. 대용량 파일 처리
2. 주기적인 데이터 수집
3. Kafka, RabbitMQ 등의 메시지 큐 처리
4. 비동기 이메일 전송 등
위의 작업을 위해 별도의 스레드를 생성하여 사용한다.
Spring Boot API 스레드
1) 톰캣 내부의 스레드 풀이 스레드 생성
2) HTTP 요청이 올 때 실행
3) 요청 처리 용도
4) 컨테이너(Tomcat)가 관리
5) http-nio-8080-exec-1 등의 이름으로 명시
직접 구현한 Worker (Runnable 등)
1) 개발자가 직접 스레드 생성
2) 필요할 때 명시적으로 실행
3) 백그라운드 작업, 비동기 처리 용도
4) 개발자가 Thread 또는 Executor로 직접 관리
5) 직접 설정하거나 기본 이름 사용
코드로 비교해 보면,
@RestController
public class Controller {
@GetMapping("/hello")
public String hello() {
// 이미 요청 처리용 스레드에서 실행됨
return "Hello";
}
}
public class Worker implements Runnable {
public void run() {
System.out.println("작업 수행 중...");
}
}
new Thread(new Worker()).start(); // 메인 흐름과는 별개로 실행 됨
Spring Boot의 API는 내부적으로 톰캣 스레드 풀의 스레드에서 실행되므로, 개발자가 별도로 스레드를 만들지 않아도 멀티스레딩 환경에서 동작한다. 하지만, 요청과는 별도로 동작하는 지속적인 작업, 병렬 작업을 처리하려면 Runnable, Thread, @Async, ExecutorService 등을 사용해 직접 스레드를 만들어야 한다.
정의 : 하나의 프로세스 안에서 여러 개의 스레드가 동시에 실행되도록 하는 것
(단, "동시에"라는 건 CPU 스케줄링에 의해 논리적으로 동시에 보인다는 의미)
예) 이미지 필터링 작업을 4조각으로 나눠서 4개의 스레드가 각자 병렬로 처리함.
전체 작업: 큰 이미지 필터링
ㄴ Thread-1: 왼쪽 위
ㄴ Thread-2: 오른쪽 위
ㄴ Thread-3: 왼쪽 아래
ㄴ Thread-4: 오른쪽 아래
큰 작업을 나눠서 동시에 여러 스레드가 처리한다.
CPU 코어가 많을수록 빠르게 작업이 끝난다.
예)
작업의 종류가 다르다.
동시에 병렬로 동작하여 앱 전체 성능을 향상 시킬 수 있다.
멀티스레딩이지만 다른 개념
작업 리스트: [작업 A, 작업 B, 작업 C, 작업 D]
스레드 풀: Thread-1, Thread-2
작업 A -> Thread-1
작업 B -> Thread-2
작업 C -> Thread-1 (끝나고 다시 할당됨)
작업 D -> Thread-2 (끝나고 다시 할당됨)
즉,
마지막으로,
1. Worker 여러 개를 Thread로 생성해서 실행 하는 경우와
Worker worker1 = new Worker();
Worker worker2 = new Worker();
Thread t1 = new Thread(worker1);
Thread t2 = new Thread(worker2);
t1.start();
t2.start();
Worker worker1 = new Worker();
Worker worker2 = new Worker();
worker1.run(); // 단순 메서드 호출
worker2.run(); // 단순 메서드 호출
1번의 경우 멀티스레딩이지만 2번은 아니다.
Runnable 인터페이스를 구현했다 해도, Thread로 start() 하지 않으면 새로운 스레드가 만들어 지지 않아서 하나의 스레드씩 동기적으로 실행하는 싱글스레드 환경(2번)