ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring] 싱글톤, 의존성 주입, 멀티 스레드, 스레드 세이프
    2/Spring 2023. 9. 14. 11:20

    개발을 하다가 쉘스크립트를 통해 모듈을 구동하는 코드를 작성하였는데, 처리시간이 5초 이상 걸리는 작업이었다.

    만약에 이 컨트롤러에 여러 클라이언트가 동시에 요청을 보내게 된다면 어떻게 되는걸까?

    앞에 요청이 마무리될 때까지 뒤에 요청은 기다려야 하는걸까?

    스레드풀에 있는 스레드가 다 할당될 때까지는 연속해서 요청을 받고 스레드가 다 할당되면 그 때 위와 같은 일이 일어날까?

    스프링은 싱글톤인데 어떻게 멀티스레드 환경에서 스레드 세이프할 수 있을까?

    등의 생각을 하게 되었다. 이해한대로 정리해보고자 한다.

     

    기초적인 설명은 거의 없어서 처음 공부하시는 분들은 이해에 어려움이 있을 수 있습니다.

    싱글톤 (Singleton)

    스프링을 배우면 처음부터 접하게 되는 단어인 듯하다.

    객체를 계속 생성하면 메모리를 계속 차지하기 때문에 객체를 처음 한번만 생성하여 동일한 객체를 계속 돌려서 쓰는 것이다.

    public class Clazz {
        private static Clazz instance = null;
    
        private Clazz();
        
        public static Clazz getInstance() {
        	if (instance == null) instance = new Clazz();
            return instance;
        }
    }

    위와 같이 멤버 변수에 자신의 객체를 담을 수 있도록 선언해두고, 생성자는 외부에서 생성할 수 없도록 private 으로 막아둔 뒤

    getInstance 라는 정적 메소드를 통해서만 이미 생성된 객체를 받아서 사용할 수 있도록 하는 것이다.

     

    스프링에서 싱글톤이라는 단어가 나오는 것은 스프링에서 관리하는 객체를 빈이라고 하는데 이때 빈은 싱글톤으로 관리된다.

    특정 어노테이션을 통해서 싱글톤이 아니라 매번 객체가 생성되도록 할 수도 있다.

     

    의존성 주입 (Dependency Injection)

    의존성 주입 또한 스프링에서 꼭 접하게 되는 용어인데, 기존 자바 코드에서 객체를 사용하기 위해서는 new 연산자를 통해 객체를 생성해주어야 했지만, 스프링에서는 @Autowired 라는 어노테이션을 통해 멤버 변수에 빈을 주입시켜준다.

    다시 말해서 new 생성자로 객체를 생성하지 않고 객체를 주입받아서 사용하는 것이다.

    @Component
    public class Service{
        ....
    }
    
    @Controller
    public class Controller{
    	@Autowired Service service;
    }

    @Component 혹은 @Bean 어노테이션을 통해 빈으로 관리되는 클래스를, 멤버 변수에서 주입 받아서 사용할 수 있다.

    주입방식에 따라서 필드, 세터, 생성자 주입 방식이 있다.

     

    멀티 스레드 (Multi Thread)

    스레드에 관하여 아직 깊게 생각하고 다뤄본 적이 없는데, 스프링은 기본적으로 멀티 스레드 환경이라고 한다.

    스레드 라는 것은 요청을 맡아서 처리하는 하나의 리소스라고 할 수 있을 것 같다.

    그렇기 때문에 스레드가 생성될 수록 서버의 자원(CPU 혹은 메모리) 를 사용하게 되고, 계속 생성하다 보면 자원 부족으로 OutOfMemory 와 같은 치명적인 에러가 발생하게 된다.

    그래서 스레드풀을 통해서 정해진 스레드 개수를 가지고 돌려가면서 사용하게 된다.

    예를 들어 가용 스레드가 50개이고 동시에 들어온 요청이 100개라고 한다면 일단 50개의 요청을 먼저 처리하고 나머지는 기다리고, 처리가 완료되면 다음 50개의 요청을 처리하는 방식이다.

     

    이때 생각할 수 있는 것은 싱글톤에 의해 객체는 하나인데 여러개의 스레드에서 동시에 쓰이게 된다면 동시성 문제 혹은 비슷한 문제가 발생하게 되지 않을까? 라는 생각이 들게 된다.

     

    스레드 세이프 (Thread Safe)

    멀티스레드 환경에서 동시성과 같은 문제가 발생하지 않고 즉 스레드간 간섭 없이 처리가 가능한 것을 스레드 세이프라고 하는데,

    한번도 스프링으로 개발을 하면서 스레드 세이프하지 않은 케이스를 만난적이 없어서 스프링에서 알아서 처리를 잘 해주는줄 알았다.

    하지만 당연하게도 스프링 빈 자체가 스레드 세이프하지는 않다고 한다.

    싱글톤으로 관리 되기 때문에 멤버 변수에 mutable 한 값이 존재한다면 값이 변경되는 과정에서 의도하지 않은 값을 사용하게 된다.

     

    그럼 왜 사용하면서 문제를 느껴본 적이 없을까 하면, 그냥 배운대로 사용했기 때문이다.

    멤버 변수에는 immutable 한 값들만 선언을 하여 사용하고, 다른 변수는 지역변수로 사용하도록 배웠고 그게 당연했기 때문이다.

     

    멤버 변수는 힙 메모리에 올라가게되어 모든 스레드에서 접근 가능한 공유 상태에 있게 되고, 지역 변수는 함수 호출 시 스택 메모리에 올라가게 되어 스레드간 공유가 불가능한 상태가 된다.

     

    그렇기 때문에 스프링에서 스레드 세이프하게 코드를 작성하는 것은 멤버 변수에는 immutable 한 값 즉 final 과 같이 선언하여 값이 수정되는 일이 애초에 없도록 하는 것이라고 한다.

     

    그래서 의존성 주입 시에도 꼭 순환참조의 문제 뿐만이 아니라 빈을 immutable 한 상태로 주입 받을 수 있도록, final 로 선언하여 주입 받는 생성자 주입 방식을 사용하는 것을 권장한다.

     

     

     

     

Designed by Tistory.