인스턴스를 오직 한 개만 제공하는 클래스
시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러 개 일 때 문제가 생길 수 있는 경우가 있다. 인스턴스를 오직 한 개만 만들어 제공하는 클래스가 필요하다.

구현 방법 1
private 생성자에 static 메서드
public class Settings {
private static Settings instance;
private Settings() { }
public static Settings getInstance() {
if (instance == null) {
instance = new Settings();
}
return instance;
}
}
- 생성자를 private으로 만든 이유
- 외부에서 생성자 사용을 못하게 하여 new를 사용한 인스턴스화를 막기 위한 목적이다.
- getInstance() 메서드를 static으로 선언한 이유
- 인스턴스 생성 없이 getInstance() 호출을 가능하게 하기 위한 목적이다.
- getInstance()가 멀티 쓰레드 환경에서 안전하지 않은 이유
- instance가 아직 인스턴스화 되지 않은 상황에서 첫 번째 쓰레드가 if문 안으로 들어왔으나 아직 new를 실행하지 않은 상태일 때 다른 쓰레드가 if문 안으로 들어온다면 서로 다른 인스턴스가 생성되게 된다.
구현 방법 2
동기화(synchronized)를 사용해 멀티쓰레드 환경에 안전하게 만드는 방법
public static synchronized Settings getInstance() {
if (instance == null) {
instance = new Settings();
}
return instance;
}
- 자바의 동기화 블럭 처리 방법은?
- 메서드 앞에 synchronized를 붙이거나, 메서드 내의 코드 일부를 블럭으로 감싸고 블럭 앞에 synchronized을 붙인다.
- 블럭 영역 안으로 들어가면서부터 쓰레드는 지정된 객체의 lock을 얻게 되고, 이 블럭을 벗어나면 lock을 반납한다.
- 해당 객체의 lock을 가지고 있는 쓰ㄷ레드만 임계 영역의 코드를 수행할 수 있다.
- getInstance() 메소드 동기화시 사용하는 락(lock)은 인스턴스의 락인가 클래스의 락인가? 그 이유는?
- static 메서드에 동기화 블럭을 설정하면 클래스 단위로 락이 걸린다.
구현 방법 3
이른 초기화 (eager initialization)을 사용하는 방법
private static final Settings INSTANCE = new Settings();
private Settings() {}
public static Settings getInstance() {
return INSTANCE;
}
- 이른 초기화가 단점이 될 수도 있는 이유?
- 클래스가 메모리에 할당되는 시점에 생성되므로 생성되는데 리소스가 많이 필요하거나 변수를 사용하지 않는 경우 불필요한 리소스 소모가 발생한다.
- 만약에 생성자에서 checked 예외를 던진다면 이 코드를 어떻게 변경해야 할까요?
- try-catch를 사용하여 unchecked 예외로 변환시켜야 한다.
구현 방법 4
double checked locking으로 효율적인 동기화 블럭 만들기
- volatile 키워드를 사용해야하고, java 1.5 이상에서만 동작함
public static Settings getInstance() {
if (instance == null) {
synchronized (Settings.class) {
if (instance == null) {
instance = new Settings();
}
}
}
return instance;
}
- double check locking이라고 부르는 이유?
- 인스턴스의 생성 여부를 체크 하고, synchronized 블럭 안에서 한번 더 체크하기 때문이다.
- 첫 번째 체크를 통과한 쓰레드들만 동기화 블럭에 적용된다.
- instacne 변수는 어떻게 정의해야 하는가? 그 이유는?
- volatile 키워드를 사용해야 한다.
- 인스턴스를 메인 메모리에 저장하고 읽기 때문에 값 불일치 문제를 해결할 수 있다.
구현 방법 5
static inner 클래스를 사용하는 방법
- 멀티쓰레드에 안전한 방법, 지연 로딩 가능, 구현이 복잡하지 않음
private Settings() {}
private static class SettingsHolder {
private static final Settings INSTANCE = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.INSTANCE;
}
- 이 방법은 static final를 썼는데도 왜 지연 초기화(lazy initialization)라고 볼 수 있는가?
- inner class는 클래스가 처음 사용될 때 초기화가 수행된다.
- getInstance()를 호출하는 시점에 inner class인 SettingsHolder에 접근하게 되고, 이때 초기화가 수행되기 때문에 지연 초기화처럼 수행되게 된다.
싱글톤 패턴 구현 깨트리는 방법 1
리플렉션을 사용한다면?
Settings settings = Settings.getInstance();
Constructor<Settings> declaredConstructor = Settings.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Settings settings1 = declaredConstructor.newInstance();
System.out.println(settings == settings1);
- 리플렉션에 대해 설명하세요.
- 구체적인 클래스 타입을 알지 못해도, 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
- setAccessible(true)를 사용하는 이유는?
- private 생성자에 접근하기 위한 목적이다.
싱글톤 패턴 구현 깨트리는 방법 2
직렬화 & 역직렬화를 사용한다면?
Settings settings = Settings.getInstance();
Settings settings1 = null;
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {
out.writeObject(settings);
}
try (ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))) {
settings1 = (Settings) in.readObject();
}
System.out.println(settings == settings1);
- 자바의 직렬화 & 역직렬화에 대해 설명하세요.
- 직렬화: 자바 시스템 내부에서 사용되는 객체 또는 데이터를 바이트(byte) 형태로 변환
- 역직렬화: 바이트로 변환된 데이터를 다시 객체로 변환
- SerializableId란 무엇이며 왜 쓰는가?
- 직렬화된 클래스의 버전을 기억하여 로드된 클래스와 직렬화된 객체가 호환되는지 확인한다.
- SerializableId가 다르면 역직렬화 할 수 없다.
- try-resource 블럭에 대해 설명하세요.
- try 코드 블럭이 끝나면 자동으로 자원을 종료해주기 때문에 명시적으로 자원 반환을 하지 않아도 된다.
직렬화 & 역직렬화는 방지 가능하다.
protected Object readResolve() {
retuen getInstance();
}
구현 방법 6
enum을 사용하는 방법
- 리플랙션에 안전한 유일한 방법
public enum Settings {
INSTANCE;
}
- enum 타입의 인스턴스를 리플랙션을 만들 수 있는가?
- enum 타입은 리플랙션을 할 수 없도록 막혀있어서 리플랙션에 안전하다.
- enum으로 싱글톤 타입을 구현할 때의 단점은?
- 클래스가 메모리에 할당되는 시점에 인스턴스가 미리 만들어진다. 초기화 시점이 문제가 되지 않다면 가장 안전한 방법이다.
- enum은 상속이 불가능하다.
- 직렬화 & 역직렬화 시에 별도로 구현해야 하는 메서드가 있는가?
- enum 타입은 enum 클래스를 상속받게 되는데, enum 클래스는 Serializable을 이미 구현하고 있기 때문에 추가적인 구현이 필요 없다.
실무 적용 사례
- 스프링에서 빈의 스코프 중에 하나로 싱글톤 스코프
- 자바 java.lang.Runtime
- 다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리 등) 구현체의 일부로 쓰이기도 한다.
예제 코드
'GoF의 디자인 패턴' 카테고리의 다른 글
어댑터(Adapter) 패턴 (0) | 2022.01.13 |
---|---|
프로토타입(Prototype) 패턴 (0) | 2022.01.10 |
빌더(Builder) 패턴 (0) | 2022.01.08 |
추상 팩토리 패턴 (0) | 2022.01.06 |
팩토리 메소드(Factory method) 패턴 (0) | 2022.01.04 |
댓글