Singleton Pattern

chaming·2021년 10월 23일
0

JAVA

목록 보기
2/4

🧡Singleton이란 ?

어떤 클래스가 최초에 한 번만 메모리에 할당되고(static) 그 메모리에 대해서 객체를 만들어 사용하는 디자인 패턴.

✅사용하는 이유는 ?

  • 하나의 인스턴스가 생성되는 경우에 사용하며, 여러 쓰레드가 동시에 해당 인스턴스를 공유하여 사용할 수 있다. 요청이 많은 곳에서 사용하면, 효율성을 증가할 수 있다.

  • 전역성을 가지고 다른 인스턴스와 공통으로 사용되는 경우 메모리 관점으로 봤을때, 매우 유용한 방식이다.

  • Ex) 프린터 공유 , 예매시스템 등의 시스템에서 사용할 수 있다.

✅사용시 주의 사항

  1. 안티패턴이다.... 사실 싱글톤 패턴을 직접 사용하기 위험요소가 있다.

  2. 싱글톤을 잘못사용하게 되는 경우, 다른 객체간의 결함도가 높아져 객체 지향 설계 원칙에 어긋난다!!

  3. 싱글톤 인스턴스 하나의 수정으로 인해, 다른 곳에 영향을 주게 된다.

  4. Multi-Thread 환경에서의 Thread-Safe 문제 발생 > 🔆동시성(Concurrency)에 대한 문제점을 고려

그렇다면, 이제 Singleton의 다양한 구현 방식을 살펴보자.


🧡Singleton in Java 구현 방식

자바에서 싱글톤 패턴이란 , private construtor와 static method를 사용한다.

1.Eager Initialization (이른 초기화, Thread-safe 보장 X)

public class Singleton {
    
    // 1.Eager Initailization
    // static키워드를 사용하여 정적바인딩
    private static Singleton uniqueInstance = new Singleton();
    

    private Singleton(){}

    public static Singleton getInstance(){
        return uniqueInstance;
    }
    public void print(String input){
        System.out.println(input);
    }

}

static 키워드를 이용하여, 클래스 로더가 초기화 하는 시점에 해당 인스턴스를 메모리에 등록 (정적바인딩 )하며, private로 접근제한자가 지정이 되어 있기 때문에 외부접근도 불가능하며 , 새로 인스턴스를 생성하는 것조차 불가능하다. 곧, getInstance() 메소드만을 이용하여 인스턴스를 제어할 수 있기 때문에 싱글톤패턴을 구현했다고 볼 수 있다.

하지만, 특정 Thread가 동시에 getInstance()를 호출하는 경우 인스턴스가 두개가 발생되므로 이 방법은 Multi-Thread 환경에서 Thread-safe가 보장되지 않는 방식이다.

2.Lazy Initailization with Synchronized (동기화 블럭 , Thread-safe 보장)

public class Singleton {

    // 2.Lazy Initailization
    private static Singleton uniqueInstance2;	// 초기에 인스턴스를 생성하지 않고
    private Singleton(){}
    
    public static synchronized Singleton getInstance(){
        if(uniqueInstance2 == null){
            uniqueInstance2 = new Singleton();	// 요청시점에 인스턴스 생성
        }
        return uniqueInstance2;
    }
}

1번의 Eager Initialization의 방식에서 Thread-safe가 보장되는 Lazy-Initialization 방식이다.

해당 방식은 동기화 블럭을 적용하여,

컴파일시 uniqueInstance2 인스턴스에 값을 할당하지 않고, 인스턴스가 필요한 시점에 요청하여 동적바인딩을 통해 인스턴스를 생성하며, synchronized 키워드를 이용하여 Thread-safe를 보장하는 방식이다.

그렇다면, Lazy Initialization방식이 동시성을 보장해주는 완벽한 싱글톤패턴이냐 ?

그렇지 않다. 안타깝게도 synchronized 키워드를 사용하는 순간 성능은 떨어지기 때문이다.

3.Lazy Initialization. DLC(Double Checking Locking, Thread-safe 보장)

public class Singleton {
	// 3. Lazy Initialization, DCL
    // volatile 키워드 사용, Thread-safe 보장
    private volatile static Singleton uniqueInstance3;
    private Singleton(){}

    public Singleton getInstance(){
        if (uniqueInstance3 == null){
            synchronized (Singleton.class){
                if(uniqueInstance3 == null){
                    uniqueInstance3 = new Singleton();
                }
            }
        }
        return uniqueInstance3;
    }
}

volatile 키워드를 사용하여, Multi-Thread가 진행되더라도 uniqueInstance3 인스턴스가 정상적으로 초기화 된다. 또한, 여러 Thread가 한 번에 진행되더라도 synchronized(동기화블럭)을 이용하여 Thread-safe를 보장한다.

✅ volatile 키워드

volatile 변수는 Main Memory에 값을 저장하고 읽어오기 때문에, 변수값에 대한 불일치 문제가 생기지 않는다.

  • Multi-Thread 환경에서 하나의 Thread는 read and write / 다른 Thread는 only read
  • 여러개의 Thread가 write하는 상황이라면, synchronized를 지정하여 원자성을 보장해야 한다.
  • 반대로 말하면 volatile변수를 사용하지 않는 Multi-Thread 환경에서는 CPU Cache에 값을 저장하게 되어 각각의 Thread가 갖는 변수의 값이 불일치하게 된다.

4. Lazy Initialization. LazyHolder(게으른 홀더, Thread-safe 보장)

public class Singleton {
    
	// 4. Lazy Initialization, LazyHolder
    private Singleton(){}
    
    // 내부 클래스에 static 변수 선언
    private static class InnerInstanceClass(){
        // 클래스 로딩 시점에 생성, final 키워드를 이용해 최초 한번 생성
        private static final Singleton uniqueInstance4 = new Singleton();
    }
    public static Singleton getInstance(){
        return InnerInstanceClass.uniqueInstance4;
    }
}

InnerInstanceClass클래스의 변수가 없기 때문에 static 멤버 클래스더라도, 클래스 로더가 초기화 과정을 진행할 때, InnerInstanceClass 메소드를 초기화하지 않고 getInstance()를 호출할 때 초기화가 된다. 즉, 동적바인딩의 특징을 이용하여 Thread-safe 를 보장하는 방법이다.


🧡Singleton Pattern in Spring

나는 자바개발자이다.... 그말은 곧 난 Spring에서 적용할 수 있는 방법을 찾아야한다.😅

(결론은.. Spring에서는 싱글톤패턴을 적용하지 않아도 된다!! 우리도 모르게 이미 싱글톤으로 관리를 하고 있었다...!! )

왜 Spring에서 싱글톤을 따로 적용하지 않아도 되는지 확인해보자.

✅ Singleton in Java vs Spring

Java의 싱클톤객체 생명주기는 Class인 반면 , Spring은 ApplicationContext이 기준이다.

  • Spring 환경에서 Bean을 등록할 때 범위는 default로 Singleton이고, 그 외에 prototype, request , session 등이 있다고 한다.
  • Spring에서 싱글톤을 사용할 수 있게 해주는건 applicationContext가 있기 때문이다.
  • Spring의 핵심 컨테이너, Bean을 관리해주는 BeanFactory의 핵심 구현 클래스가 바로 DefaultListableBeanFactory이며 이걸 구현하고 있는 인터페이스가 SingletonRegisty이다.

✅Why? Spring은 Bean을 Singleton으로 생성하는가?

우선 Spring의 특징을 알아야 하는데 ,

Spring Bean : Spring IoC 컨테이너가 관리하는 자바객체를 Bean이라고 한다.

자세한 설명은 스프링 빈(Bean)의 개념과 생성 원리 해당 블로그들을 참조하면, 좋을 것 같다.

IoC(Inversion of Control) : 인스턴스의 생성부터 소멸까지 컨테이너(서블릿과 같은 Bean)가 관리해주는 것을 말한다. 곧, 컨트롤의 제어권은 프레임워크의 필요에 따라 스프링에서 사용자 코드를 호출

결국, IoC 컨테이너 (ApplicationContext)에 제어권을 넘겨줌으로써 Bean을 싱글톤으로 생성할 수 있는 것이라고 합니다.

서블릿은 대부분 Multi-Thread 환경에서 싱글톤을 동작하며, 서블릿 클래스 하나당 하나의 객체를 생성하여 , 클라이언트 요청을 처리를 담당하는 Thread 들이 해당 객체를 공유해서 사용한다.


[참조사이트]

https://webdevtechblog.com/%EC%8B%B1%EA%B8%80%ED%84%B4-%ED%8C%A8%ED%84%B4-singleton-pattern-db75ed29c36

https://velog.io/@kyle/%EC%9E%90%EB%B0%94-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4-Singleton-Pattern

https://elfinlas.github.io/2019/09/23/java-singleton/

https://programmingsharing.com/implement-singleton-pattern-in-java-33e0a6f0aabb

https://velog.io/@jaeeunxo1/spring-singleton

profile
Java Web Developer😊

0개의 댓글