싱글톤 패턴이란?
인스턴스를 오직 한 개만 제공하는 클래스입니다.
시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러 개 일 때 문제가 생길 수 있는 경우가 있다.
인스턴스를 오직 한개만 만들어 제공하는 클래스가 필요하다.
싱글톤 구현 방식 1. private 생성자 및 static 메서드 사용
public class Settings {
private static Settings instance;
// 클래스 밖에서 인스턴스 생성 방지
private Settings(){}
public static Settings getInstance(){
if(instance == null) {
return instance = new Settings();
}
return instance;
}
}
해당 방식은 멀티 쓰레드 환경에서 안전하지 않다.
동시에 두개의 스레드가 접근해 new를 동시에 실행한다면 두 스레드가 가진 인스턴스는 달라질 것이다.
싱글톤 구현 방식 2. private 생성자 및 static 메서드 사용 + synchronized
private static Settings instance;
// 클래스 밖에서 인스턴스 생성 방지
private Settings(){}
public static synchronized Settings getInstance(){
if(instance == null) {
return instance = new Settings();
}
return instance;
}
synchronized 키워드를 사용하면 멀티쓰레드 환경에서 안전하다.
하지만 쓰레드가 기다리게 되는 병목현상이 나타날 우려도 있다.
자세힌 알고 싶으면 synchronized에
싱글톤 구현 방식 3. 이른 초기화(eager initalization) 사용하기
public class Settings {
private static final Settings INSTANCE = new Settings();
// 클래스 밖에서 인스턴스 생성 방지
private Settings(){}
public static Settings getInstance(){
return INSTANCE;
}
}
이른 초기화는 클래스가 로딩되는 시점에 미리 만들어 놓은 객체를 리턴해주면 되기 때문에 멀티 스레드 환경에서 안전한다.
대부분의 상황에서는 일반적인 초기화가 지연 초기화보다 낫다. [effective java 참고]
만약 해당 인스턴스를 만드는 과정이 굉장히 길고 오래 걸리고 메모리를 많이 사용함에도 불구하고 해당 객체가 잘 쓰이지 않는 상황일 때 지연초기화를 쓰는 것이 좋다.
싱글톤 구현 방식 4. double checked locking 으로 효율적인 동기화 블록 만들기
public class Settings {
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;
}
}
객체 생성을 나중에 하고 싶지만 Synchronized block 이 비용이 신경쓰인다면 다음과 같이 구현하면 된다.
인스턴스가 이미 있는 경우 Synchronized 를 skip 하게 되어 Synchronized block을
동시에 멀티스레드가 들어오는 경우에만 실행된다.
모든 스레드가 Synchronized에
그리고 인스턴스를 필요로 하는 시점에서만 만들 수 있다.
싱글톤 구현 방식 5. static inner 클래스 사용하기
public class Settings {
// 클래스 밖에서 인스턴스 생성 방지
private Settings(){}
static class SettingsHolder {
public static final Settings INSTANCE = new Settings();
}
public static Settings getInstance(){
return SettingsHolder.INSTANCE;
}
}
해당 코드는 멀티스레드 환경에서 안전하고 getInstance 호출 시 SettingsHolder 가 로딩이 되며 인스턴스를 생성하기 때문에 lazy loading 이 된다는 장점이 있다.
static inner 클래스 사용하기
이 방식으로 싱글턴 객체를 생성할 땐 어떻게 하나의 객체만 생성된다는걸 보장할 수 있을까?
클래스 참조시 클래스 로딩이 발생하며 클래스 초기화 과정이 이루어집니다.
(jvm 스펙에 따르면 ) 클래스는 로딩 및 초기화 과정이 딱 한번만 수행됩니다. 따라서 스레드 세이프하여 하나의 객체만 생성된다는 것을 보장할 수 있습니다.
또한 final 키워드가 있어 참조를 변경할 수 없기에 불변입니다.
싱글톤 패턴 구현을 깨트리는 방법
1. 리플렉션으로 싱글톤 깨뜨리기
package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class App {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Settings settings1 = Settings.getInstance();
Constructor<Settings> constructor = Settings.class.getDeclaredConstructor();
constructor.setAccessible(true);
Settings settings = constructor.newInstance();
System.out.println(settings1 == settings);
}
}
리플렉션을 통해 new을 활용한 것과 같은 인스턴스를 생성해 새로운 인스턴스를 만드는 것을 확인할 수 있다.
리플렉션으로 꺼내온 객체는 싱글톤이 깨지는 것을 확인할 수 있다.
2. 직렬화 , 역직렬화 활용
자바에는 오브젝트를 파일 형태로 디스크에 저장해 놨다가 다시 읽어드리는 직렬화, 역직렬화를 지원한다.
public class Settings implements Serializable { // 직렬화를 위해 Serializable 구현
package singleton;
import java.io.*;
public class App {
public static void main(String[] args) throws Exception {
Settings settings1 = Settings.getInstance();
Settings settings2 = null;
// 직렬화 작업
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("setting.obj"))) {
out.writeObject(settings1);
}
// 역직렬과 작업
try (ObjectInput in = new ObjectInputStream(new FileInputStream("setting.obj"))) {
settings2 = (Settings)in.readObject();
}
System.out.println(settings1);
System.out.println(settings2);
System.out.println(settings1 == settings2);
}
}
직렬화 역직렬화를 통해서도 싱글톤을 깨뜨릴 수 있다.
역직렬화시 싱글톤을 보장하기 위해 역직렬화 시 쓰이는 readResolve 메서드를 다음과 같이 정의해 주면 싱글톤을 보장할 수 있다.
protected Object readResolve() {
return getInstance();
}
package generic.sigleton;
import java.io.Serializable;
public class Settings implements Serializable {
private static final Settings INSTANCE = new Settings();
private static class SettingsHoler {
private static final Settings INSTANCE = new Settings();
}
public static Settings getInstance(){
return SettingsHoler.INSTANCE;
}
protected Object readResolve() {
return getInstance();
}
}
안전하고 단순하게 싱글톤 구현하기
리플렉션을 통해 싱글톤을 깨지는 것을 방지하는 방법은 없을까?
enum 타입 방식의 싱글턴 - 바람직한 방법
package singleton;
public enum Settings {
INSTACE;
}
enum 클래스는 내부 코드적으로 리플렉션 생성을 막아놓았다.
단점은 클래스 로딩 순간 해당 인스턴스가 미리 만들어진다는 것이다. 객체 생성 비용이 비싸지 않는다면 가장 안전한 방법이다.
enum 은 기본적으로 Serializable 를 상속받고 있고 별다른 조치를 취하지 않아도 안전하게 동일한 인스턴스로 역직렬화가 된다.
- 자바에서 enum 을 사용하지 않고 싱글톤을 구현하는 방법은?
- private 생성자를 만들고 static 메서드를 사용하여 싱글톤을 구현한다.
- private 생성자와 static 메서드를 사용하는 방법의 단점은?
- 지연 로딩 시 멀티 스레드에 안전하기 위해 추가 작업이 필요하다.
- 리플렉션으로 싱글톤을 깨뜨릴 수 있다.
- enum을 사용해 싱글톤 패턴을 구현하는 방법의 장점과 단점은?
- 리플렉션, 역직렬화로 싱글톤을 파괴하는 객체 생성을 방지할 수 있다.
- 객체가 미리 생성된다.
- static inner 클래스를 사용해 싱글톤 패턴을 구현하라.
참고 자료
GoF 의 디자인 패턴, 이팩티브 자바
'JAVA' 카테고리의 다른 글
[JAVA] 자원 정리 (try-catch , try-with-resources) (0) | 2024.11.26 |
---|---|
[JAVA] Comparable ,Comparator (0) | 2024.11.23 |
[JAVA] iterator 순회 중 만난 ConcurrentModificationException (1) | 2024.11.20 |
[JAVA ]Set , Map 알아보기, 로드 팩터 (Load Factor)와 HashSet의 리해싱 (0) | 2024.11.19 |
유니코드, UTF-8, 직렬화 (1) | 2024.11.18 |