Spring Boot starter 에 내장되어있는 tomcat 은 기본적으로 Multi Thread 환경을 지원한다.
덕분에 클라이언트의 요청을 처리하고 있는 도중 새로운 클라이언트 요청이 들어왔을 경우, 이전 요청이 끝날 때까지 기다리지 않고 다른 쓰레드가 해당 요청을 처리한다. 자칫 잘못하면 데이터의 일관성이 무너질 수 있기 때문에 해당 쓰레드간의 동기화 과정은 매우 중요하다.
보통 멀티 쓰레드 환경에서 하나의 사용자의 요청을 하나의 쓰레드로 처리한다. 동일한 ip의 요청일 때에는 같은 쓰레드로 처리할 수 있긴하지만, 일반적인 경우에는 하나의 요청에 하나의 쓰레드가 붙는다.
그러나 매번 요청이 생성되고 끝날 때마다, 쓰레드를 생성하게 되면 생성하고 삭제하는 코스트가 발생한다.
스프링에서는 이러한 코스트를 줄이기 위해, 미리 Thread Pool 을 이용하여 사용될만한 쓰레드 개수를 지정해 생성해둔다.
잘 분석해서 쓰레드 수를 정하지 않으면 자원 낭비로 이어지기 때문에, 적절한 쓰레드 수를 정하는 것도 중요하다.
또한 이러한 Thread Pool 은 내장 tomcat 에서 관리한다.
각각의 쓰레드는 독립적인 Stack 메모리 영역을 가지며, 나머지 Heap, Code, Data 메모리 영역을 다른 쓰레드들과 공유한다.
때문에 public 메소드 안에 있는 지역 변수는 독립적인 stack 메모리 영역에서 작동되기 때문에 Thread-Safe 하다.
그러나 멤버 변수처럼 공유되는 변수의 메모리 주소를 참조하여 사용할 때 데이터의 일관성이 무너질 수 있다.
스프링에서는 이러한 문제점을 해결하기 위해, 의존성 주입을 적극 활용한다.
기본적으로 스프링은 싱글톤 디자인 패턴이기 때문에 객체를 빈으로 등록한 후 또 다른 요청이 같은 객체를 사용할 때 새로 생성하는게 아닌, 기존 객체를 그대로 사용한다. (Prototype 옵션을 주게 되면 매번 요청할 때마다 프록시 객체를 생성한다.)
이 때 빈으로 등록된 객체들 ( ex Controller, Service ) 은 고유 멤버 변수를 선언하고 변경시키지 않는다.
즉 불변 객체라는 뜻이다.
해당 빈 객체들은 사용할 외부 객체 및 변수들을 의존성 주입을 통해 주입받는다.
주입 받은 함수들을 호출할 뿐이다.
주입 받는 방법은 생성자 주입, setter 주입, 필드 주입 등 다양한 방법이 있지만 일반적으로 생성자 주입을 많이 사용한다.
@Autowire 어노테이션을 사용해서 하는 방법도 있지만, 필자는 롬복에서 제공하는 생성자 자동생성 어노테이션을 주로 사용한다.
이와 같은 방식으로 싱글톤 디자인 패턴에서 스프링은 빈 객체에서 상태값을 들고있지 않는다.
다른 요청에서 값을 덮어씌우거나 변경할 가능성이 있기 때문이다.
반드시 메소드 내에서 지역 변수로 값을 관리해야한다.
해당 문제는 디버깅 하기도 어렵기 때문에 주의해야한다.
'개인 공부 > Spring' 카테고리의 다른 글
[Spring] AWS S3 Bucket 에 이미지 등록하기 (0) | 2023.02.28 |
---|---|
[Spring] Bean Scope (0) | 2022.11.22 |
[Spring] HashMap 으로 Cache 구현하기 (0) | 2022.11.14 |
[Spring] Redis vs EHcache vs HashMap (0) | 2022.11.09 |
[Spring] @Transactional 파해치기 (0) | 2022.11.08 |