Java/디자인 패턴

[디자인 패턴] 싱글톤

태감새 2023. 3. 2. 00:00

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


싱글톤

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

싱글톤 구현하기

인스턴스를 오직 한 개만 만들어야 하므로 new 연산자의 접근을 막아야 한다.

생성자를 private으로 생성

public class Settings {  
    private static Settings instance;  

    private Settings() {}  

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

Setting의 인스턴스를 static으로 선언해놓는다.
getInstance()가 처음 호출되면 instance가 null이므로 새로운 Setting을 만들어서 반환하고 그 이후에는 instance가 존재하므로(static이기 때문) 존재하는 인스턴스가 반환된다.

 

문제점

  • 멀티 쓰레드에 안전하지 못하다.
    멀티 쓰레드 환경에서는 if문 안에 들어오고 나서 종료되고 다른 쓰레드에서 인스턴스를 생성하면 여러 개의 인스턴스가 생성될 가능성이 있다. 그래서 이 문제를 해결해야 한다.

 

해결법

  1. synchronized 사용하기
public class Settings {  
    private static Settings instance;  

    private Settings() {}  

    public static synchronized Settings getInstance() {  
        if (instance == null) {  
            instance = new Settings();  
        }  
        return instance;  
    }  
}

동기화 하면 되지만 성능 저하 문제 있음

  1. 이른 초기화(eager initializer)
public class Settings {  
    private static final Settings INSTANCE = new Settings();  

    private Settings() {}  

    public static Settings getInstance() {  
        return INSTANCE;  
    }  
}

지연 생성 불가능

  1. double checked locking
public class Settings {  
// volatile : 변수를 CPU cache가 아닌 Main Memory에 저장  
    private static volatile Settings instance;  

    private Settings() {}  

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

1,2번의 문제점 모두 해결.

  • instance == null일때만 동기화 되므로 성능 저하 미미 (한 번 생성하면 동기화 할 필요 없음)
  • 생성자가 함수 안에 존재하므로 지연 생성 가능

하지만 1,2번의 코드보다 복잡하다.

  1. 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;  
    }  
}

3번의 복잡함 문제까지 해결된 버전. 지연 생성 + Thread Safe + 간단
결론적으로 추천하는 방법은 3,4번이다.

 

SettingHolder가 static class이면 바로 생성되는 것이 아닌가?

아니다. SettingHolder는 static class이기는 하지만 그 전에 inner class다. 그러므로 SettingHolder가 로딩되는 시점은 Settings 클래스가 호출되는 시점인데 생성자를 private으로 막아뒀으니 getInstance() 메서드를 호출할 때 생성되는 것이다.

 

싱글톤 부수기

  1. 리플렉션 사용하기
    1.  리플렉션?
      1. 구체적인 클래스의 타입을 알지 못해도 그 클래스의 메서드,필드에 접근하게 해주는 자바 API
      2. 런타임에 실행중인 클래스를 가져와서 실행해야 하는 경우
      3. 생성자를 private으로 막아뒀더라도 여기서 접근을 허용하고 새로운 생성자를 만들 수 있다.
  1. 직렬화 & 역직렬화
    1. 직렬화를 하고 다시 역직렬화를 할 때는 생성자를 통해서 새로운 인스턴스를 만들어준다.
    2. readResolve() 메서드를 사용하면 막을 수 있기는 함.
    3. 근데 리플렉션을 못막음

 

부수기도 막는 방법

enum을 사용하면 됨

public enum Settings {  
    INSTANCE;  
}

다 막아주지만 지연 생성이 불가능하다. 완벽한 것은 없는듯.

 

질문 답

private 생성자에 static 메서드

  1. 생성자를 private으로 만든 이유?
    • 생성자를 열어두면 여러 인스턴스를 만들 수 있기 때문에 싱글톤이 무너진다.
  2. getInstance() 메서드를 static 선언한 이유?
    • getInstance() 메서드를 static으로 선언하지 않으면 Settings 객체를 만들어서 호출해야 한다. 하지만 우리는 1번에서 생성자를 막았기 때문에 static을 선언해야 Settings에 접근할 수 있다.
  3. getInstance()가 멀티 쓰레드에 안전하지 않은 이유?
    • 위에서 설명함

싱글톤 패턴 복습

  • 자바에서 enum을 사용하지 않고 싱글톤을 구현하는 방법은?
    • 생성자 private 후 static 함수로 만들기
    • 생성자 private 후 필드에 static final로 생성자 선언해서 사용하기
    • 메서드 동기화 처리하기
    • double checking lock 사용하기
    • static inner class 사용하기
  • private 생성자와 static 메서드를 사용하는 방법의 단점은?
    • 멀티 쓰레드에 안전하지 않다.
    • 리플렉션과 역직렬화에 안전하지 못하다.
  • enum을 사용한 싱글톤 패턴 구현의 장/단점은?
    • 간단하다.
    • 리플렉션과 역직렬화에 안전하다.
    • 멀티 쓰레드에 안전하다.
    • 지연 생성이 불가능하다.