스프링MVC

[스프링 MVC] 서블릿과 쓰레드

changha. 2024. 8. 13. 16:18

서블릿이란

클라이언트의 요청을 처리하고, 그 결과를 개발자가 편하게 사용하도록 도와주는 자바 기술 

위처럼 개발자가 비즈니스 로직에만 집중할 수 있도록 나머지 과정을 자동화해주는 기술이 서블릿이다. 

서블릿 흐름을 살펴보면 아래와 같다. 

1. 웹 브라우저에서 localhost:8080/hello URL로 요청을 보낸다. 

2. WAS에서 request, response 객체를 생성한다.

3. request, response를 파라미터로 담은 Servlet을 생성한다. 
4. Servlet이 끝나면 response 객체의 내용을 기반으로 HTTP 응답을 생성한다.


서블릿 컨테이너란 

톰캣처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다. 


특징

  • 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기 관리
  • 서블릿 객체는 싱글톤으로 관리
  • 동시요청을 위한 멀티쓰레드 처리 지원


서블릿 객체를 싱글톤으로 관리한다는 것에서 의문점


request, response응답은 요청마다 다를 것 아닌가?
그러면 싱글톤으로 관리한다는 것은 request, response 객체를 제외한 서블릿 템플릿을 말하는건가?

이에 대해 찾아보니까 비슷한 질문이 이미 올라와있었다. 

[질문 내용]
안녕하세요 김영한 개발자님. 항상 좋은 강의 감사합니다.
강의를 듣다가 궁금한 점이 생겨서 질문드려요. 9:39초에서 http request, response는 고객마다 데이터가 다 다르기 때문에 요청이 올때마다 계속 생성하는게 맞다는 것은 이해가 갑니다. 그런데, 왜 서블릿은 싱글톤으로 만들어야 되는지 잘 이해가 가지 않습니다. 제가 생각하기에는 만약 고객1이 /spring을 요청하고 고객2가 /hello를 요청한다면 이 둘의 데이터도 다르기 때문에 각자 생성해야되지 않나 싶어요ㅠㅠ 10:19초에서 말씀하신 것처럼 개별 요청이 오면 애플리케이션 로직을 작성하고 또 다른 요청이 오면 같은 서블릿에 또 다른 애플리케이션 로직을 작성하고.... 이런 식으로 동작하는 걸까요? 감사합니다.


[답변 내용]
안녕하세요. qzxy812님, 공식 서포터즈 David입니다.

/hello 를 처리하는 HelloServlet, /spring을 처리하는 SpringServlet이 각각 존재한다면

HelloServlet, SpringServlet를 매번 요청이 올 때마다 새롭게 만들어줄 필요없이 한 번만 생성한 뒤 이미 생성된 것을 사용하면 됩니다.

강의 내에서 말씀하신 싱글톤은 HelloServlet, SpringServlet을 각각 하나만 존재하도록 하는 것입니다.

: 그러니까 동일한 URL로 유저들이 계속 보낼 때 서블릿 객체를 한번 생성하고 버리는 것은 비효율적이므로 계속 하나를 가지고 사용한다고 이해했다.


그럼 서블릿을 누가 호출하나? 쓰레드

 

쓰레드란?

애플리케이션 코드를 순차적으로 하나하나 실행하는 것

특징

  • 자바 메인 메서드를 처음 실행하면 main이라는 이름의 쓰레드가 실행
  • 쓰레드가 없다면 자바 애플리케이션 실행이 불가능
  • 쓰레드는 한번에 하나의 코드 라인만 수행
  • 동시 처리가 필요하면 쓰레드를 추가로 생성 


요청이 많아질 수록 쓰레드도 그만큼 필요하게 된다.

근데 쓰레드를 필요할 때마다 생성하면 단점이 발생한다.


단점 

  • 생성 비용이 비싸다.
  • 컨텍스트 스위칭 비용이 발생한다. 
    • CPU가 쓰레드를 전환할 때마다 발생하는 비용 
  • 쓰레드 생성에 제한이 없다.
    • 고객 요청이 너무 많이 오면, CPU, 메모리 임계점을 넘어서 서버가 죽을 수 있다.

그래서 위와 같은 상황을 해결하기 위해 쓰레드 풀이 등장한다.


쓰레드 풀

필요한 쓰레드를 쓰레드 풀에 보관하고 관리한다. 

여기서 필요한만큼 꺼내서 쓰고 다시 반납한다. 
최대치를 설정하여 너무 많은 요청이 들어왔을 때도 기존 요청은 안전하게 처리할 수 있다. 



멀티 쓰레드 환경에서 싱글톤 패턴 문제점  

문제점

public class Settings {
    private static Settings instance = null;

    private Settings() {}

    public static Settings getInstance() {
        if (instance == null) {
            instance = new Settings();
        }
        return instance;
    }
}

1. 쓰레드 A가 if문 조건을 통과하여 new Settings()를 호출하기 직전, 쓰레드 B도 같은 조건을 통과할 수 있다.

2. 결과적으로 두 개의 쓰레드가 동시에 new Settings()를 호출하여 두 개의 인스턴스가 생성될 수 있다.

3. 이 경우, 싱글톤의 본질인 '하나의 인스턴스만 존재해야 한다'는 조건이 깨진다.

 

해결책

크게 두가지 방식으로 해결할 수 있다.

Lazy Holder 방식을 선호한다. 

  • 이중 잠금 방식
public class Settings {
    private static volatile Settings instance = null;

    private Settings() {}

    public static Settings getInstance() {
        if (instance == null) {
            synchronized (Settings.class) {
                if (instance == null) {
                    instance = new Settings();
                }
            }
        }
        return instance;
    }
}

코드가 복잡하고 성능 저하 가능성이 있다. 

  • Lazy Holder 
public class Settings {
    private Settings() {}

    private static class InstanceHolder {
        private static final Settings INSTANCE = new Settings();
    }

    public static Settings getInstance() {
        return InstanceHolder.INSTANCE;
    }
}

멀티쓰레드 환경에서 안전하며, 성능도 우수하고 코드가 간결하다. 


스프링부트로만 프로젝트에 바로 진입하다보니 스프링 내부 동작에 대해 잘 몰랐었다.
이번 기회에 확실히 정리하며 넘어가려고 한다. 
가장 먼저 WAS, HTTP 처리 방식, 서블릿 그리고 쓰레드에 대해 알아보았다.