빠에야는 개발중

싱글톤 패턴 본문

공부/디자인패턴

싱글톤 패턴

빠에야좋아 2018. 3. 15. 11:34

싱글톤을 사용하는 이유

싱글톤 패턴은 인스턴스를 어플리케이션 내에서 단 한번 생성하여 계속 사용하는 패턴이다.(동일한 객체) 이렇게 하면 우선 메모리를 아낄 수 있고, 전역 객체인 싱글톤 객체를 여러 메소드에서 공유 자원으로 사용하도록 할 수도 있다. 이는 주로 DBCP 같은 공통된 객체를 여러개 사용해야하는 상황에서 많이 사용한다.

문제점

싱글톤을 과도하게 사용하면 다른 인스턴스 간의 결합도가 높아져 OCP를 위반할 가능성이 있다. 이에 따라 수정 하기도 어렵고, 테스트도 어려워진다.

멀티 쓰레드 환경에서 동기화 처리를 제대로 해주지 않으면 여러개의 인스턴스가 생기는 등의 문제가 발생할 수 있다.

그래서 꼭 필요한 경우가 아니라면 사용을 지양하는 것이 좋다. (아니면 시스템에 맡겨버리던가)

싱글톤을 구현하는 방법

일반적인 방법

static으로 인스턴스를 생성하여 static 메소드로 리턴해준다. 이 방법은 클래스가 로딩 될 때 항상 인스턴스를 생성하기 때문에 메모리 낭비가 있을 수 있고, 동기화로부터 안전하지 못하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class EagerInitialization {
 
    private static EagerInitialization instance = new EagerInitialization();
 
    private EagerInitialization () {
        System.out.println"call EagerInitialization constructor." );
    }
 
    public static EagerInitialization getInstance () {
        return instance;
    }
    
    public void print () {
        System.out.println("It's print() method in EagerInitialization instance.");
        System.out.println("instance hashCode > " + instance.hashCode());
    }
}
cs

Lazy loading

클래스가 로딩 될 때마다 인스턴스가 생기는 것을 방지하기 위해서 인스턴스를 가져올 때 생성하도록 변경해줬다. 이로써 메모리 낭비를 방지할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LazyInitialization {
    
    private static LazyInitialization instance;
    private LazyInitialization () {}
    
    public static LazyInitialization getInstance () {
        if ( instance == null )
            instance = new LazyInitialization();
        return instance;
    }
    
    public void print () {
        System.out.println("It's print() method in LazyInitialization instance.");
        System.out.println("instance hashCode > " + instance.hashCode());
    }
}
cs

synchronized

synchronized 키워드를 사용하여 메소드를 동기화한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ThreadSafeInitalization {
    
    private static ThreadSafeInitalization instance;
    private ThreadSafeInitalization () {}
    
    public static synchronized ThreadSafeInitalization getInstance () {
        if (instance == null)
            instance = new ThreadSafeInitalization();
        return instance;
    }
    
    public void print () {
        System.out.println("It's print() method in ThreadSafeInitalization instance.");
        System.out.println("instance hashCode > " + instance.hashCode());
    }
    
}
 
cs

DCL(Double Check Locking)

동기화 한 것은 좋았으나 메소드 단위이기 때문에 성능상 좋지 못하다. 그래서 사용하는 방법이 메소드 내에서 블록으로 감싸는 것이다. DCL은 synchronized 블록 방법을 사용하여 인스턴스의 유무를 두 번 검사하기 때문에 붙은 이름이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ThreadSafeInitalization {
    
    private static ThreadSafeInitalization instance;
    private ThreadSafeInitalization () {}
    
    public static ThreadSafeInitalization getInstance () {
        if (instance == null) {
            synchronized {
                if (instance == null) {                
                    instance = new ThreadSafeInitalization();
                }
            }
        }
        return instance;
    }
    
    public void print () {
        System.out.println("It's print() method in ThreadSafeInitalization instance.");
        System.out.println("instance hashCode > " + instance.hashCode());
    }
    
}
 
cs

Holder에 의한 초기화

클래스 내에 또다른 클래스를 선언하여 jvm 클래스 로더의 특징을 이용했다. 실수를 일으킬 수 있는 개발자의 손을 떠나 시스템에 맡김으로써 좋은 패턴 구현을 이루어냈다.

1
2
3
4
5
6
7
8
9
10
11
12
public class InitializationOnDemandHolderIdiom {
    
    private InitializationOnDemandHolderIdiom () {}
    private static class Singleton {
        private static final InitializationOnDemandHolderIdiom instance = new InitializationOnDemandHolderIdiom();
    }
    
    public static InitializationOnDemandHolderIdiom getInstance () {
        System.out.println("create instance");
        return Singleton.instance;
    }
}
cs


싱글톤을 무너뜨리는 방법

원본을 수정하지 않고 작업을 해야 할때(ex: 테스트) reflection을 사용하여 싱글톤을 깨뜨릴 수 있다. java의 reflection은 매우 강력해서 private으로 선언되어 있어도 강제로 가져와서 새로운 인스턴스를 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UsingReflectionToDestroySingleton {
    
    public static void main (String[] args) {
        EagerInitialization instance = EagerInitialization.getInstance();
        EagerInitialization instance2 = null;
        
        try {
            Constructor[] constructors = EagerInitialization.class.getDeclaredConstructors();
            for ( Constructor constructor : constructors ) {
                constructor.setAccessible(true);
                instance2 = (EagerInitialization)constructor.newInstance();
            }
        } catch (Exception e) {
            
        }
        
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        
    }
}
cs


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

빌더 패턴  (0) 2018.03.19
템플릿 메소드 패턴  (0) 2018.03.14
팩토리 패턴  (0) 2018.03.14
Comments