어떤 클래스가 최초에 한 번만 메모리에 할당되고(static) 그 메모리에 대해서 객체를 만들어 사용하는 디자인 패턴.
하나의 인스턴스가 생성되는 경우에 사용하며, 여러 쓰레드가 동시에 해당 인스턴스를 공유하여 사용할 수 있다. 요청이 많은 곳에서 사용하면, 효율성을 증가할 수 있다.
전역성을 가지고 다른 인스턴스와 공통으로 사용되는 경우 메모리 관점으로 봤을때, 매우 유용한 방식이다.
Ex) 프린터 공유 , 예매시스템 등의 시스템에서 사용할 수 있다.
안티패턴이다.... 사실 싱글톤 패턴을 직접 사용하기 위험요소가 있다.
싱글톤을 잘못사용하게 되는 경우, 다른 객체간의 결함도가 높아져 객체 지향 설계 원칙에 어긋난다!!
싱글톤 인스턴스 하나의 수정으로 인해, 다른 곳에 영향을 주게 된다.
Multi-Thread 환경에서의 Thread-Safe 문제 발생 > 🔆동시성(Concurrency)에 대한 문제점을 고려
그렇다면, 이제 Singleton의 다양한 구현 방식을 살펴보자.
자바에서 싱글톤 패턴이란 , private construtor와 static method를 사용한다.
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가 보장되지 않는 방식이다.
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 키워드를 사용하는 순간 성능은 떨어지기 때문이다.
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가 갖는 변수의 값이 불일치하게 된다.
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 를 보장하는 방법이다.
나는 자바개발자이다.... 그말은 곧 난 Spring에서 적용할 수 있는 방법을 찾아야한다.😅
(결론은.. Spring에서는 싱글톤패턴을 적용하지 않아도 된다!! 우리도 모르게 이미 싱글톤으로 관리를 하고 있었다...!! )
왜 Spring에서 싱글톤을 따로 적용하지 않아도 되는지 확인해보자.
Java의 싱클톤객체 생명주기는 Class인 반면 , Spring은 ApplicationContext이 기준이다.
- Spring 환경에서 Bean을 등록할 때 범위는 default로 Singleton이고, 그 외에 prototype, request , session 등이 있다고 한다.
- Spring에서 싱글톤을 사용할 수 있게 해주는건 applicationContext가 있기 때문이다.
- Spring의 핵심 컨테이너, Bean을 관리해주는 BeanFactory의 핵심 구현 클래스가 바로 DefaultListableBeanFactory이며 이걸 구현하고 있는 인터페이스가 SingletonRegisty이다.
우선 Spring의 특징을 알아야 하는데 ,
Spring Bean : Spring IoC 컨테이너가 관리하는 자바객체를 Bean이라고 한다.
자세한 설명은 스프링 빈(Bean)의 개념과 생성 원리 해당 블로그들을 참조하면, 좋을 것 같다.
IoC(Inversion of Control) : 인스턴스의 생성부터 소멸까지 컨테이너(서블릿과 같은 Bean)가 관리해주는 것을 말한다. 곧, 컨트롤의 제어권은 프레임워크의 필요에 따라 스프링에서 사용자 코드를 호출
결국, IoC 컨테이너 (ApplicationContext)에 제어권을 넘겨줌으로써 Bean을 싱글톤으로 생성할 수 있는 것이라고 합니다.
서블릿은 대부분 Multi-Thread 환경에서 싱글톤을 동작하며, 서블릿 클래스 하나당 하나의 객체를 생성하여 , 클라이언트 요청을 처리를 담당하는 Thread 들이 해당 객체를 공유해서 사용한다.
[참조사이트]
https://elfinlas.github.io/2019/09/23/java-singleton/
https://programmingsharing.com/implement-singleton-pattern-in-java-33e0a6f0aabb