본문 바로가기

디자인패턴

디자인 패턴 - 싱글톤 패턴

728x90
반응형

인스턴스를 오직 한 개만 제공하는 클래스

- 시스템 런타임, 환경세팅에 대한 정보 등 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다.

인스턴스를 오직 한 개만 만들어 제공하는 클래스에 필요하다.

 

new 를 이용해서 인스턴스를 생성하면 얼마든지 생성할 수 있고 같은 클래스라 하더라도 같은 인스턴스가 아니다.

 

java에서 new를 이용해서 새로운 인스턴스를 생성하지 못하게 하려면 private을 이용해서 클래스내부 생성자를 만든다.

 

객체가 null일때 내부에서 new로 인스턴스를 생성

public class Settings {

    private static Settings settings;
    
    private Settings() {};
    
    public static getInstance() {
    	if(settings == null) {
        	settings = new Settings();
          }
          return settings;
    
    }
}

위와 같이 사용할 수 있지만 위의 방법은 멀티쓰레드에 안전하지 않다.

 

synchronized 키워드 사용

쓰레드세이프하기위해서는 synchronized 키워드를 사용할 수 있다.

public static synchronized Settings getInstance() {
	
}

이 방법의 문제는 synchronized를 사용하면 비용이 많이 든다.

getInstance를 호출할 때마다 비용이 발생한다.

 

이른 초기화

public class Settings {
    private static final Settings settings = new Settings();
    
    private Settings() {}
    
    public static Settings getInstance() {
        return settings;
    }
}

미리 초기화를 하면 synchronized를 사용할 필요가 없다.

 

더블체크하기

public class Settings {
    private static volatile Settings settings;

    private Settings() {

    }

    public static Settings getInstance() {
        if (settings == null) {
            synchronized (Settings.class) {
                if (null == settings) {
                    settings = new Settings();
                }
            }
        }
        return settings;
    }
}

null체크를 두 번 한다.

멀티쓰레드가 빈번하게 일어나는 경우 훨씬 유리하다.

volatile은 자바 1.5이상부터 동작한다.

 

static inner class만들기

public class Settings {

    private Settings() {

    }

    private static class SettingsHolder {
        private static final Settings settings = new Settings();
    }

    public static Settings getInstance() {
        return SettingsHolder.settings;
    }
}

static inner클래스로 만든 후 호출하면 좀 더 간편한 로직으로 구현이 가능하다.

 

BUT!

리플렉션을 이용하면 싱글톤이 깨진다.

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
  IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        Settings settings = Settings.getInstance();

        Constructor<Settings> constructor = Settings.class.getDeclaredConstructor();
        constructor.setAccessible(true);

        Settings settings2 = constructor.newInstance();

        System.out.println(settings == settings2);

    }

 

직렬화 역직렬화 사용시 싱글톤이 깨진다.

public class Settings implements Serializable {

    private Settings() {

    }

    private static class SettingsHolder {
        private static final Settings settings = new Settings();
    }

    public static Settings getInstance() {
        return SettingsHolder.settings;
    }
}
public static void main(String[] args) throws IOException, ClassNotFoundException {

        Settings settings = Settings.getInstance();
        Settings settings2 = null;

        try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {
            out.writeObject(settings);
        }

        try (ObjectInput input = new ObjectInputStream(new FileInputStream("settings.obj"))) {
            settings2 = (Settings) input.readObject();
        }

        System.out.println(settings == settings2);
    }

 

직렬화&역직렬화는 readResolve()메소드를 Settings안에 구현하면 해결할 수 있다.

 protected Object readResolve() {
        return getInstance();
    }

 

 

enum을 사용하는 방법

public enum Settings {
    INSTANCE;

    private Settings() {
    }

}

리플렉션을 막아놨다.

그래서 유일한 인스턴스가 보장된다. 

단점은 미리 만들어진다는 것이다. 상속을 사용할 수 없다.

직렬화 역직렬화에 안정적이다.

 

 

 

출처: 코딩으로 학습하는 GoF의 디자인 패턴

728x90
반응형