Spring - 싱글톤 패턴(Singleton Pattern)
싱글톤 패턴이란?
기본전략 : 인스턴스가 사용될 때 똑같은 인스턴스를 만들어 내는 것이 아니라, 동일 인스턴스를 사용하게끔 하는 것.
프로그램상에서 동일한 커넥션 객체를 만들거나, 하나만 사용되야하는 객체를 만들 때 매우 유용하다.
Eager initialization
public class SingletonEx {
//private static으로 선언
private static EagerInitialization instance = new EagerInitialization();
//생성자 private로 선언
private SingletonEx () {
System.out.printlnt("call SingletonEx<<<<<");
}
//조회 method
public static SingletonEx getInstance(){
return instance;
}
public void print(){
System.out.println("It's print() method in instance");
System.out.println("instance hashCode > " + instance.hashCode());
}
}
전역변수로 instance를 만드는데 private static을 사용해서 만들어준다.
static이 붙은 클래스 변수는 인스턴스화에 상관없이 사용이 가능하게 된다.
하지만 앞에 private 접근제어자를 붙여주어 체이닝으로의 접근은 불가능하게 한다.
이 상태에서 생성자를 private로 명시한다.
생성자를 private로 하면 new 키워드를 사용할 수 없다.
즉, 다른 클래스에서 Single s = new Single(); 이런 방법을 통한 인스턴스 생성은 불가능해진다.
결국 인스턴스를 가지려면 getInstance () 메소드를 사용해야한다.
위와 같은 싱글톤 패턴은 리소스가 작은 프로그램일 때는 고도화 대상이 아니다.
하지만 수 많은 클래스에서 싱글톤 패턴을 사용한다고 할 때는 얘기가 달라진다.
lazy initialization
클래스 인스턴스가 사용되는 시점에 인스턴스를 생성
public class LazyInitialization{
private static LazyInitialization instance;
private LazyInitialization() {
}
public static LazyInitialization getInstance (){
if(instance == null){
instance = new LazyInitialization();
}
return instance;
}
public void print(){
System.out.println("It's print() method in instance")
System.out.println("instance hashCode > " + instance.hashCode());
}
}
위 코드에서 주목해야 할 부분은 new LazyInitialization(); 부분이다.
instance가 null 인경우에만 new를 사용해 객체를 생성한다.
최초 사용시점에서만 인스턴스화를 시키기 때문에, 프로그램이 메모리에 적재되는 시점에 부담이 많이 줄게된다.
하지만 multi thread 방식이라면??
위와 같은 singleton pattern은 안전하지 않다.
만약 스레드가 여러개 돌면서 같은 시점에 getInstance() method를 호출한다면 인스턴스가 두번 생길 위험이 있다.
thread safe initialization
multi thread 문제를 해결하기 위해 동기화를 사용해 singlethon pattern을 구현한다
public class ThreadSafeInitialization{
private static ThreadSafeInitialization instance;
private ThreadSafeInitialization() {
}
public static ThreadSafeInitialization getInstance (){
if(instance == null){
instance = new ThreadSafeInitialization();
}
return instance;
}
public void print(){
System.out.println("It's print() method in instance")
System.out.println("instance hashCode > " + instance.hashCode());
}
}
여러 thread들이 동시에 접근해서 인스턴스를 생성시키는 위험은 없어졌다.
하지만 수 많은 thread들이 getInstance() 메소드를 호출하게 되면 높은 cost 비용으로 인해 프로그램 전반에 성능 저하가 일어난다.
enum initialization
이펙티브 자바 책에서 enum singleton 방법이 소개 되었다.
enum이 singleton pattern으로 사용될 수 있는 이유는 아래와 같다.
instance가 생성될 때, multi thread로 부터 안전하다.(추가된 method는 그렇지 않을수도)
단 한번의 인스턴스 생성을 보장한다.
사용이 간편하다.
enum value는 자바 프로그램 전역에서 접근이 가능하다.
public enum EnumInitialization {
INSTANCE;
static String test = "";
public static EnumInitialization getInstance(){
test = "test";
return INSTANCE;
}
}
initialization on demand holder idiom
JVM의 class loader의 매커니즘과 class load 시점을 이용해 내부 class를 생성시켜 thread간 동기화 문제를 해결한다.
public class InitializationOnDemandHolderIdiom {
private InitializationOnDemandHolderIdiom () {
}
private static class Singleton{
private static final InitializationOnDemandHolderIdiom
instance = new InitializationOnDemandHolderIdiom();
}
public static InitializationOnDemandHolderIdiom getInstance (){
System.out.printlkn("create instance");
return Singleton.instance;
}
}
현재 java에서의 singleton은 거의 이 방법을 사용한다고 한다.
그렇다면 누군가 singleton으로 작성한 코드를 원본 수정없이 작업하려면 어떻게 해야할까?
using reflection to destroy singleton
public class UsingReflectionToDestroySingleton {
public static void main (String[] args){
SingletonEx instance = SingletonEx.getInstance();
SingletonEx instance2 = null;
try{
Constructor[] constructors = SingletonEx.class.getDeclaredConstructors();
for(Constructor constructor : constructors){
constructor.setAccessible(true);
instance2 = (SingletonEx)constructor.newInstance();
}
} catch (Exception e){
}
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
}
}
위 코드를 실행하면 두 hashCode 값이 다른것은 확인 할 수 있다.
자바의 reflection은 매우 강력해서, 생성자가 private 일지라도 강제로 가져와 새로운 인스턴스 생성이 가능하다.