Singleton Pattern (with java)

이동욱·2023년 11월 19일
post-thumbnail

Singleton Pattern이란?

하나의 웹 서비스가 있다고 가정을 해보자 해당 서비스에는 사용자가 접속 시 인사말을 출력해주는 WelcomeService가 존재한다. 접속자 수가 적을때는 요청마다 WelcomeService를 생성하여 인사말을 출력해주는것이 가능하지만 접속자수가 많아지면 매 요청에 대하여 WelcomeService를 생성하는것은 리소스를 많이 요구한다.
이때 Singleton Pattern을 사용 할 수 있다. 해당 패턴은 사용자의 요청이 오기 전에 미리 1개의 WelcomeService를 생성해 놓고 사용자가 요청을 하면 미리 생성된 WelcomeService에서 해당 요청을 처리한다.
간단히 요약하면 Singleton Pattern은 객체의 인스턴스를 1개만 생성하는 패턴이다.


Singleton Pattern 장단점.

장점

  • 객체를 하나만 생성하여 메모리를 보다 효율적으로 관리 할 수 있다.
  • 미리 인스턴스를 생성하여 기존의 방법보다 요청 처리속도가 빠르다.
  • 해당 패턴으로 생성된 객체의 인스턴스의 전역이기때문에 다른 객체의 인스턴스들이 데이터를 공유하기 쉽다.

단점

  • 싱글톤 인스턴스를 사용하는 객체들은 모두 해당 인스턴스에 의존하기에 재사용성이 떨어지고 테스트하기 어렵다.
  • 싱글톤 인스턴스들은 모두 전역이기에 다른 클래스에서 싱글톤 인스턴스의 값을 변경하면 싱글톤 클래스를 사용하든 모든 클래스에 영향을 끼친다.
  • 기본생성자를 사용할 수 없기에 상속이 불가능하다.

Java에서의 Singleton Pattern 구현방법.

  1. Eager Initialization
  • static을 통해 해당 클래스를 Class Loader가 로딩할 때 객체를 생성해 준다. 이 방법은 객체를 사용하지 않더라도 Class Loader가 해당 객체를 무조건 생성하기에 리소스 낭비가 발생 할 수 있다.
class Singleton {

	private static Singleton singleton = new Singleton(); 
	
	private Singleton() {} 
	
	public static Singleton getInstance() {
		return singleton;
	}
}
  1. Static Block Initialization
  • Static Block Initialization으로 Eager Initialization 방법과 비슷하지만 Static Block을 사용하여 Exception 처리를 해주는 방법이다. 하지만 이 방법도 클래스 로딩 단계에서 객체를 생성하기 때문에 자원의 비효율성을 해결할 수 없다.
class Singleton {
	private static Singleton singleton; 
	
	private Singleton() {} //생성자에 접근 x
	
	static {
		try {
			singleton = new Singleton();
		} catch (Exception e) {
			throw new RuntimeException("Exception occured in creating singleton instance");
		}
	}
	
	public static Singleton getInstance() {
		return singleton;
	}
}



Static Bolck은 초기화 블록(Initialization Block)이라고 불리며 클래스가 처음 로딩될 때 한 번만 수행되는 블록을 의미한다. 잘 사용하지는 않지만 클래스 변수의 복잡한 초기화에 사용된다. 비슷한 개념으로 Instance Block이 존재한다.
Static Block : 클래스가 로딩될때 한번만 수행
Instance Block : 인스턴스가 생성될때마다 수행
  1. Lazy Initialization
  • static으로 선언된 getInstance() 메서드를 통해 객체를 생성해 주는 방법이다. 싱글톤 패턴은 한 객체를 여러 곳에서 접근할 수 있기 때문에 멀티 스레드 환경에서 동기화 문제가 발생할 수 있다. 만약 한 번에 여러 곳에서 getInstance() 메서드를 호출한다면 여러 개의 객체가 생성될 수 있기 때문이다.
class Singleton {
	private static Singleton singleton; 
	
	private Singleton() {}
	

	public static Singleton getInstance() {
		if (singleton == null) singleton = new Singleton();
		return singleton;
	}
}
  1. Thread Safe Singleton
  • 세 번째 방법의 동기화 문제를 해결하기 위한 방법으로 Java에서 동기화를 해결하기 위한 키워드인 synchronized를 걸어주는 방법이다. synchronized 키워드를 사용하면 어떤 한순간에는 하나의 스레드 만이 임계 영역(Critical Section) 안에서 실행하는 것이 보장된다.
    해당 방법은 인스턴스를 생성 한 후 접근을 할 때에서 synchronized를 호출하여 성능저하가 발생 할 수 있다.
class Singleton {
	private static Singleton singleton; 
	
	private Singleton() {}
	
	public static synchronized Singleton getInstance() {
		if (singleton == null) singleton = new Singleton();
		return singleton;
	}
}

Double Checked Locking 방식은 객체가 null 일 경우에만 synchronized가 실행되도록 하여 객체가 생성된 후에는 synchronized가 실행되지 않게하여 성능저하를 막을 수 있다.

class Singleton {
	private static Singleton singleton; 

	private Singleton() {}


	public static Singleton getInstance(){
	    if(singleton == null){
	        synchronized (Singleton.class) {
	            if(singleton == null) singleton = new Singleton();
	        }
	    }
	    return singleton;
	}
}
  1. Bill Pugh Singleton Implementation
  • Inner Static Helper Class를 사용하는 방법으로 현재 가장 널리 사용되고 있는 싱글톤 패턴 구현 방법이다. 해당 방법은 SingletonHelper가 Ineer class로 구현되어 ClassLoader에 의해 시작 시 생성되지 않고 getInstance를 호출 시 생성된다. 또한 ClassLoader에 의해 생성되기에 multi-thread 환경에서도 안전하다.
class Singleton {

	private Singleton() {} 

	private static class SingletonHelper {
		private static final Singleton SINGLETON = new Singleton();
	}
	
	public static Singleton getInstance(){
	    return SingletonHelper.SINGLETON;
	}
}
  1. Enum Singleton
  • Enum을 사용하여 싱글톤 패턴을 구현하는 방법이다. Eager Initialization, Static Block Initialization 방식처럼 Lazy Loading이 아니기 때문에 자원의 비효율성을 해결해 주지 못하는 단점도 존재한다.
enum EnumSingleton {
	INSTANCE;

	public static void doSomething(){
        //do something
    }
}
profile
Backend Developer

0개의 댓글