티스토리 뷰

디자인 패턴

01. 싱글톤 패턴

jin-park 2022. 2. 10. 14:35

1. 의미

  • 시스템 런타임이나 환경설정과 같이 오직 하나의 인스턴스만 만들어 제공할때 사용하는 패턴.

2. 작성 방법

  • new 키워드를 사용하여 인스턴스를 생성하지 못하도록 막는다.
  • static 메서드를 통해 인스턴스를 가져오는 하나의 통로를 만든다.
public class Settings {
    private static Settings instance = null;
 
    private Settings() { }
 
    public static Settings getInstance() {
        if (instance == null) {
            instance = new Settings();
        }
 
        return instance;
    }
}
public class App {
 
    public static void main(String[] args) {
        Settings settings01 = Settings.getInstance();
        Settings settings02 = Settings.getInstance();
 
        System.out.println(settings01 == settings02); // true
    }
}

3. Thread Safe 구현 방법

  • 위의 구현은 멀티 쓰레드 환경에서는 헛점이 발생할 수 있다.
public static Settings getInstance() {
    // A쓰레드가 Null체크 후 초기화를 하려고 if문에 들어가려는 상태에서
    // B쓰레드가 A쓰레드가 초기화 직전에 Null체크를 하는경우 A, B두 개의 쓰레드가 다른 Settings인스턴스를 가질수 있음.
    if (instance == null) {
        instance = new Settings();
    }
 
    return instance;
}

3.1.  synchronized

  • synchronized 키워드를 사용하여 getInstance메서드를 하나의 쓰레드만 사용할수 있도록 한다.
  • 메서드를 사용할때 마다 동기화 처리를 하기때문에 성능에 이슈가 생길 수 있음.
public static synchronized Settings getInstance() {
    if (instance == null) {
        instance = new Settings();
    }
 
    return instance;
}

3.2. 이른 초기화

  • 인스턴스를 초기화하는 비용이 많지 않다면 미리 초기화를 시킨다.
public class Settings {
    private static final Settings INSTANCE = new Settings();
 
    private Settings() { }
 
    public static Settings getInstance() {
        return INSTANCE;
    }
}

3.3.  double checked lock

  • 위의 방법들 보다 확실하지만 구현하고 이해하기 복잡한 코드가 되버림.
public class Settings {
    private static volatile Settings instance = null;
 
    private Settings() { }
 
    public static Settings getInstance() {
        if (instance == null) {
            synchronized (Settings.class) {
                if (instance == null) {
                    instance = new Settings();
                }
            }
        }
         
        return instance;
    }
}

3.4.  static inner class 사용 (권장)

  • 내부에  static 클래스를 만들어 그안에 멤버 상수로 인스턴스를 가지고있는다.
  • 반환시에 static inner 클래스를 통하여 반환하면 반환시점에 클래스가 로딩되어 지연 초기화가 가능하며 멀티쓰레드 환경에서도 안전하다.
public class Settings {
    private Settings() { }
 
    private static class SettingsHolder {
        private static final Settings INSTANCE = new Settings();
    }
 
    public static Settings getInstance() {
        return SettingsHolder.INSTANCE;
    }
}

4. 싱글톤 패턴을 깨는 방법

  • 우회적인 방법으로 인해 싱글톤 패턴을 피해서 새로운 인스턴스를 만들수 있다.

4.1.  reflection 사용

public class App {
 
    public static void main(String[] args)
        throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Settings settings = Settings.getInstance();
 
        Constructor<Settings> settingsConstructor = Settings.class.getDeclaredConstructor();
        settingsConstructor.setAccessible(true);
        Settings settings1 = settingsConstructor.newInstance();
 
        System.out.println(settings == settings1); // false
    }
}

4.2.  직렬화 / 역직렬화 사용

  • Serializable 한 객체를 직렬화 후에 역직렬화를 하면 새로운 객체를 생성하게 된다.
public class App {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Settings settings = Settings.getInstance();
        Settings settings1 = null;
 
        try (ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream("setting.obj"))) {
            objectOutput.writeObject(settings);
        }
 
        try (ObjectInput objectInput = new ObjectInputStream(new FileInputStream("setting.obj"))) {
            settings1 = (Settings) objectInput.readObject();
        }
 
        System.out.println(settings == settings1); // false
 }
}

4.2.1.  역직렬화 대응 방안

  • 역직렬화시에 readResolve 메서드를 호출하여 객체를 생성하는데 여기서 기존 객체를 반환시킨다.
public class Settings implements Serializable {
    private Settings() { }
 
    private static class SettingsHolder {
        private static final Settings INSTANCE = new Settings();
    }
 
    public static Settings getInstance() {
        return SettingsHolder.INSTANCE;
    }
     
    protected Object readResolve() {
        return getInstance();
    }
}

5. 가장 안전하게 싱글톤을 만드는 방법

  • enum을 사용하여 만들면 reflection(enum은 reflection이 막혀있음), 직렬화/역직렬화에 안전하다.
  • 다만, 선언과 동시에 초기화 된다는 단점이 존재한다.
public enum Settings {
    INSTANCE;
}

위글은 인프런의 코딩으로 학습하는 GoF의 디자인 패턴강의를 정리하였습니다.

백기선님의 수락으로 정리하였으며 더 자세한 내용은 강의를 수강하시기 바랍니다.

https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/dashboard

 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com

 

'디자인 패턴' 카테고리의 다른 글

06. 어댑터 패턴  (0) 2022.02.10
05. 프로토타입 패턴  (0) 2022.02.10
04. 빌더패턴  (0) 2022.02.10
03. 추상 팩토리 패턴  (0) 2022.02.10
02. 팩토리 메소드 패턴  (0) 2022.02.10
댓글