[Spring] Spring의 Multi Thread, Singleton, Thread-safe
기술/Java & Spring

[Spring] Spring의 Multi Thread, Singleton, Thread-safe

반응형

✏️개요

앞서 작성한 Collection Framework에 대한 게시글은 현재 실무에서 사용되는 코드를 분석하는 과정에서 필자가 부족하다고 느껴 작성한 글이었다. 이번 주제 역시 그 과정에서 궁금증이 생겨 알아보게 된 내용을 작성해보고자 한다.

 

이 게시글의 시작은 실무 코드에 작성되어 있던 Volatile이라는 키워드였다. 이는 취업을 준비하는 과정 및 학부생 과정에서도 접해본 적 없는 키워드였기에, 이에 대해서 조사해보던 중 Thread와 관련된 키워드라는 사실을 알 수 있었다. 그러다 보니 자연스레 Thread-safe에 관련된 내용과 Spring의 Singleton 등 자세히 살펴보지 않았던 내용들을 찾게 되었고 필자 스스로가 Thread에 대한 내용을 괜히 무서워 피하기도 했고 Spring의 bean이 Singleton으로 생성된다는 것에 대한 내용 역시 이번 기회에 자세히 알아보고자 이에 대한 글을 우선 작성한 뒤, 다음 게시글에서 Volatile에 대해서 작성하고자 한다. 

 

❗ 본 게시글은 필자 개인적인 의견이므로 틀린 부분이 있을 수 있습니다. 댓글을 통해 지적해주시면 감사하겠습니다.

❗ 본 게시글에서 언급하는 웹 서버는 실무에서 사용하는 Spring의 Tomcat을 기준으로 작성되었습니다.

✏️Spring에 대해 새롭게 알게 된 것들

필자는 앞서 언급했듯이, Thread에 대한 두려움도 있었지만 Spring Framework라는 기술을 인터넷 무료 강의를 통해 독학하였기 때문에 아직까지도 정확한 Spring의 동작 과정에 대해선 알지 못한다고 생각하지만, 이번 기회에 조금 더 Spring에 대해 알게 되었다고 생각한다. (남들에겐 기초 지식일 수 있다.)

 

✏️멀티 스레드

우선 멀티 스레드란, 일반적으로 하나의 프로세스에 하나의 스레드가 작업하는 것과 달리 하나의 프로세스 내에서 여러 스레드가 동시에 작업을 수행하는 것을 말한다. 이때 여기서 말하는 스레드는 프로세스 내의 작업 단위이다.

 

필자가 여러 글을 보며 이해한 바로는 우리가 웹 애플리케이션을 이용할 때 여러 Client의 요청이 날아왔을 때, 모든 사용자가 자신이 원하는 작업을 원활히 할 수 있는 이유가 멀티 스레드 때문이라는 것이다. 만약 싱글 스레드 환경이라면, 웹 애플리케이션에 접속해 요청을 전달한 최초 사용자에게 응답을 보내기 전까지는 다른 사용자들이 해당 서비스를 이용할 수 없을 것이다. 때문에 대다수의 웹 서버는 멀티 스레드를 이용한다고 하며, Spring의 Tomcat 역시 이에 속한다고 한다.

물론 싱글 스레드 방식을 이용하는 웹 서버도 존재한다고 한다. 대표적으로 Node.js

즉, 요약하자면 Spring의 Tomcat을 포함한 대다수의 웹 서버는 멀티 스레드 방식을 따르고 Client의 요청이 있을 때마다 Thread를 생성하여 해당 요청을 처리한다고 한다. 단, 이러한 방식에서 요청하는 Client의 숫자가 많아지게 되면 그만큼 Thread를 생성/수거하는 비용 및 오버헤드가 발생하게 된다.

이와 같은 문제점을 개선하기 위해서 Thread pool이라는 것을 사용하게 되는데, 이는 Client 요청에 따라 생성해야 할 Thread를 미리 만들어 두어 요청이 오는 즉시 처리할 수 있도록 하는 것이다. 이로 인해 스레드 생성/수거 비용 및 오버헤드에 대한 단점을 개선하거나 Thread를 재사용할 수 있다는 장점이 존재하지만, 적절한 pool을 유지하지 않으면 메모리 낭비로 이어질 수 있다는 단점이 존재한다.

✏️Singleton

필자는 취업을 준비하는 과정에서 얼핏 Spring의 Bean들은 기본적으로 모두 싱글턴 패턴(Sigleton pattern)으로 제공된다는 글을 봤었던 기억이 난다. 소프트웨어 디자인 패턴 중 하나인 싱글턴 패턴에 대한 개념은 알고 있었지만, 왜 Spring에서 모든 Bean들을 싱글턴 객체로 생성되는지 의문을 가지고 있었고 자료를 찾아보는 과정에서 이를 해결할 수 있었다.

 

앞서 설명한 멀티 스레드 단락과 함께 알게 되었더니 이유는 정말 단순하다고 생각되었다. 아래의 내용을 보자.

  • 우리가 인터넷에서 흔하게 찾을 수 있는 스프링의 정의는 자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크이다. 
  • 여기서 주목할 것은 엔터프라이즈 개발로, 기업을 대상으로 하는 개발을 의미하며 이는 동시에 서비스를 이용하고자 하는 사용자가 많을 것이라는 의미이다.
  • Spring의 Tomcat을 포함한 웹 서버는 멀티 스레드 환경을 이용한다.

위의 특징을 가지는 환경에서 싱글턴 패턴을 사용하지 않는다면, 수많은 사용자들로 인해 발생하는 요청을 멀티 스레드로 생성된 수많은 스레드가 처리하는 과정마다 필요한 객체를 생성해야 할 것이다. 이는 시스템적 성능 저하 및 굉장한 메모리 낭비가 발생하는 결과를 낳을 것이다.

 

결국 Spring에서 싱글턴 패턴을 제공하는 이유는 위와 같은 위험을 관리하기 위해 모든 Bean을 싱글턴 객체로서 생성하여 모든 사용자들의 Thread가 공유할 수 있도록 만든 것이다.

✏️Thread-safe

위의 내용을 간략히 하자면 Spring Framework는 기본적으로 Multi Thread 방식을 사용하며, Singleton pattern을 이용해 모든 Bean들을 공유한다는 것이다. 그리고 이것을 그림으로 나타내면 아래와 같다. 

인터넷에 많이 퍼져있는 이미지

 

그러나 학부생 시절의 운영체제 시간에 배운 내용에 따르면, 위와 같은 상황에서는 분명 Thread-safe문제가 발생하기 때문에 이를 위한 동기화 과정이 필수적일 텐데, 필자가 지금까지 사용해온 Spring에서는 Thread-safe에 대한 문제를 신경 쓴 적이 없었다. 그리고 여러 자료를 접하기 전까지는 그 이유가 Spring 내부적으로 무언가 조치를 취해준다고 생각했다. 

 

그러나 Spring은 기본적으로 Thread-safe하지 않은 환경이라고 한다. 즉, Spring 자체는 Thread-safe하지 않지만, 우리가 흔히 널리 퍼져 알고 있는 Spring의 사용 방법이 바로 Thread-safe 한 방법이기 때문에, 이에 대한 문제를 느낄 수 없었다는 것이다.

 

앞서 멀티 스레드 환경은 요청하는 Client에 따라 Thread pool에서 Thread를 생성해 처리한다고 했는데, 여기서 Thread가 가지는 특징이 지역 변수를 저장하는 Stack 영역은 각 Thread 별로 할당되지만, 전역 변수를 저장하는 Heap 영역과 함께 Code, Data 영역은 공유한다는 것이다.

불변 객체는 Java에서 class의 instance가 생성된 시점부터 내부 상태가 일정하게 유지되는 객체,
가변 객체는 Java에서 class의 instance가 생성된 이후에도 내부 상태 변경이 가능한 객체를 의미.
전역 변수로 상태를 가지지 않고 지역 변수를 통해 값을 변경(파라미터 등 전달받은 값)하는 경우가 불변 객체,
전역 변수를 가지고 Thread 별로 공유되는 객체의 전역 변수를 변경할 수 있는 경우가 가변 객체이다.

즉, 싱글턴 패턴으로서 공유되는 객체가 상태를 가지고 변경시킬 수 있는 가변 객체라면 Thread-safe 하지 않다. 그렇기에 우리들의 Spring 코드에서 생성한 Bean(@Controller, @Service, @Repository, @Component 어노테이션이 달린 객체 등)에서 전역 변수로 VO, DTO, Map 같은 가변 객체들이 아닌, 불변 객체들이 자리하고 있는 것이라고 한다.

우리가 처음부터 배우는 예제들에서 싱글턴 객체들이 모두 불변 객체로서 활용되어 있어서 우리는 자연스레 Thread-safe 한 환경을 만든 것이다.

 

✏️마치며

자세한 내용은 양도 많고 이해하기 어려워 생략된 부분이 많은 것 같지만, 차근차근 알아가야겠다.

반응형