본문 바로가기
프로그래밍/디자인 패턴(프로그래밍)

[Design Pattern]Singleton이란?

by 그래도동 2019. 1. 17.
728x90
반응형

Singleton이란?

 프로그램상에서 동일한 인스턴스를 만들어 내는 것이 아닌 동일 인스턴스를 사용하게 하는 것

 동일한 컨넥션 객체를 만든다던지, 하나만 사용되어야 하는 객체를 만들때 사용한다.

 매우 자주 쓰이는 패턴중 하나이다.


Singleton Pattern들

 Eager Initialization

  가장 기본적인 Singleton Pattern.

  먼저 클래스 내에 전역변수로 instance 변수를 생성하고 private static을 사용하여 인스턴스화에 상관없이 접근이 가능하면서 동시에 private 접근제한자를 사용하여 class.instance로 바로 접근 할 수 없도록 한다. 또 생성자에도 private 접근제한자를 붙여서 다른 클래스에서 new 방식의 새로운 인스턴스를 생성하는 것을 방지한다.

오로지 전역변수가 포함된 클래스 내에 getInstance() 같이 메서드를 만들어 사용하여 인스턴스에 접근하도록 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package SingleTonExample;
 
public class EagerInitialization {
 
    // private static으로 전역변수 선언
    private static EagerInitialization instance = new EagerInitialization();
     
    // private 생성자
    private EagerInitialization() {}
     
    // 인스턴스 리턴 Method
    public static EagerInitialization getInstance() {
        return instance;
    }
 
}

static 전역변수로 생성되었으므로 클래스가 로딩될 때 객체가 생성되므로 Thread-Safe하다.

하지만 객체의 사용유무와 상관없이 클래스가 로딩되면 항상 객체가 생성되고, 메모리를 사용하므로 객체 생성비용이 적은 경우나 항상 쓰는 객체일 때 쓴다.


 Lazy Initialization

 인스턴스 생성 시점이 인스턴스가 사용되는 시점이다. 따라서 객체 사용전까지는 메모리를 점유하지 않는다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package SingleTonExample;
 
public class LazyInitialization {
 
// private static으로 전역변수 선언
    private static LazyInitialization instance;
 
// private 생성자
    private LazyInitialization(){}
     
// 인스턴스 리턴 Method
// 인스턴스가 null일 때만 생성
    public static LazyInitialization getInstance(){
        if(instance == null){
            instance = new LazyInitialization();
        }
        return instance;
    }
 
}


객체가 필요할 때 인스턴스를 얻을 수 있다. Eager Initialization 단점 보완.

하지만 multi-thread환경이라면 인스턴스가 두개 혹은 여러개 생성될 여지가 있다. Singleton 아니게 될 수 있다.


 Thread sage Lazy Initialization

Lazy Initialization 단점을 보완하기위해 synchronized 를 사용한 방식.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package SingleTonExample;
 
public class ThreadSafeLazyInitialization{
 
// private static으로 전역변수 선언
    private static ThreadSafeLazyInitializationinstance;
 
// private 생성자
    private ThreadSafeLazyInitialization(){}
     
// 인스턴스 리턴 Method
// synchronized 사용
    public static synchronized ThreadSafeLazyInitializationgetInstance(){
        if(instance == null){
            instance = new ThreadSafeLazyInitialization();
        }
        return instance;
    }
 
}


thread-safe하지만 synchronized를 쓰게 되면 내부적으로 multi-thread에 안전한 환경을 만들기 때문에 많은 비용이 발생한다. 따라서 성능저하가 일어날 수 있다.


 Thread sage Lazy Initialization + Double-checked locking

위의 방식에서 성능저하를 방지하기위해 만든 방법.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package SingleTonExample;
 
public class ThreadSafeLazyInitialization {
 
// private static으로 전역변수 선언
    private static ThreadSafeLazyInitialization instance;
 
// private 생성자
    private ThreadSafeLazyInitialization(){}
     
// 인스턴스 리턴 Method
// instance생성을 두번 체크하여 성능저하 보완
    public static ThreadSafeLazyInitialization getInstance(){
        
        if(instance == null){
            synchronized (ThreadSafeLazyInitialization.class) {
                if(instance == null)
                    instance = new ThreadSafeLazyInitialization();
            }
 
        }
        return instance;
    }
}


 Initialization on demand holder idiom

클래스 안에 Holder클래스를 만들어 Lazy initialization 방식을 가져가면서 thread 동기화 문제를 해결하는 방식이다. 중첩클래스인 Holder는 GetInstance Method가 호출되기 전까지는 참조되지 않으며 최초로 호출될 때 객체를 생성한다. static을 이용하여 한 번만 호출되게하고 final을 써서 다시 값이 할당되지 않게 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package SingleTonExample;
 
public class InitializationOnDemandHolderIdiom {
 
// private 생성자
    private InitializationOnDemandHolderIdiom(){}
     
// private static Holder 클래스
    private static class SingleTonHolder{
        private static final InitializationOnDemandHolderIdiom instance = new InitializationOnDemandHolderIdiom();
    }
     
// 인스턴스 리턴 Method
// Holder클래스에서 생성한 객체를 불러온다.
    public static InitializationOnDemandHolderIdiom getInstance(){
        return SingleTonHolder.instance;
    }
}


Singleton Pattern중 가장 많이 사용한다고 한다.


 Initialization on demand holder idiom

이번에는 enum의 특징을 이용한 방법이다. enum type들은 프로그램 내에서 한 번 초기화되는 점을 이용함.


1
2
3
4
5
6
7
8
9
10
package SingleTonExample;
public enum EnumSingleTon {
 
        INSTANCE;
static String test = "";
        public static EnumInitialization getInstance()
{
test = "test";
return INSTANCE;
}


Enum이 생성될 때 multi thread로부터 안전하다. (추가된 Method들은 safed 하지 않을 수 있다.)

단 한번의 인스턴스 보장하고 사용이 간편하다.



Reference

 http://limkydev.tistory.com/67

 https://blog.seotory.com/post/2016/03/java-singleton-pattern

728x90
반응형

댓글