반응형

싱글턴 패턴(Singleton Pattern)이란?


전역 변수를 사용하지 않고 객체를 하나만 생성 하도록 하며, 

생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴 ‘생성(Creational) 패턴’의 하나 


하나의 인스턴스만을 생성하는 책임이 있으며 getInstance 메서드를 통해 모든 클라이언트에게 동일한 인스턴스를 반환하는 작업을 수행한다. 


생성(Creational) 패턴 


객체 생성에 관련된 패턴 객체의 생성과 조합을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공한다. 





싱글턴 패턴(Singleton Pattern) 구현


1. 일반적인 싱글턴 패턴 생성


1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
    private static Singleton Instance;
 
    private Singleton(){}
 
    public static Singleton getInstance(){
        if (Instance == null){
            Instance = new Singleton();
        }
        return Instance;
    }
 }
cs


위에서 언급했듯이 그대로 구현하면 위와 같은 코드를 만들 수 있다.


객체가 없으면 객체를 생성하고, 있으면 존재했던 객체를 반환해주면 된다.


인스턴스가 필요한 상황이 닥치기 전에는 아예 인스턴스를 생성하지 않게 되고 이런 방법을 게으른 인스턴스 생성(lazy instantiation)라고 한다.




2. 동기화를 적용한 싱글턴 패턴


하지만 위의 코드대로라면 멀티 스레딩 방식에서 동기화가 되지 않을 것이다.


A,B 스레드가 있을 때 A 스레드가 if문에 들어가서 null인 것을 체크하고 생성하기 직전에 컨텍스트 스위칭으로 인해

B스레드가 if문에 들어가 싱글턴 객체를 만들고 다시 컨텍스트 스위칭을 하여

A스레드가 if문에서 객체 생성을 하면 총 2개의 객체가 생성됨을 알 수 있다.


따라서 우리는


1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
    private static Singleton Instance;
    
    private Singleton(){}
    
    public static synchronized Singleton getInstance(){
        if (Instance == null){
            Instance = new Singleton();
        }
        return Instance;
    }
}
cs


위와 같은 코드를 통해 동기화를 해야한다.(synchronized)


하지만 synchronized 키워드를 사용하면 사용하지 않을 때 보다 100배정도 느려진다.


따라서 동기화는 되지만 다른 방법을 생각해야 한다.




3. 미리 생성하는 싱글턴 패턴


1
2
3
4
5
6
7
8
9
 public class Singleton {
    private static Singleton Instance = new Singleton();
 
    private Singleton(){}
 
    public static synchronized Singleton getInstance(){
        return Instance;
    }
}
cs


처음부터 만드는 방법도 존재한다.


하지만 이렇게 만들면 안전할 순 있지만, 미리 객체를 만들어 놓고 전혀 사용되지 않는다면 리소스 낭비가 발생 할 것이다.




4. DCL(Double-Checking Locking), 이중 검사 방법


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
    private volatile static Singleton Instance;
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        if (Instance == null){
            synchronized (Singleton.class){
                if(Instance== null){ 
                    Instance = new Singleton();
                }
            }
        }
        return Instance;
    }
}
 
cs


이 코드는 인스턴스를 한번 확인하고 동기화를 내부에서 해주고 있다.

메서드에서 동기화 역할을 해주고 있으므로 synchronized의 비용을 절감시킬 수 있다.


하지만 이 코드는 멀티 코어 환경에서 동작할 때, 하나의 CPU를 제외 하고 나머지 CPU가 lock걸리게 되어 다른 방법을 생각해야 한다.


여기서 나오는 volatile 키워드는 아래와 같다.

volatile 키워드를 사용하면 자바의 일종의 최적화인 리오더링(보통 컴파일 과정에서 일어나며, 프로그래머가 만들어낸 코드는 컴파일 될 때 좀더 빠르게 실행될 수 있도록 조작이 가해져 최적하됨)을 회피하여 읽기와 쓰기순서를 보장 멀티스레딩을 쓰더라도 uniqueInstance변수가 Singleton 인스턴스로 초기화 되는 과정이 올바르게 진행되도록 할 수 있다. 


그럼에도 불구하고 DCL은 자바1.5이상의 버전에서만 사용가능하다. 

자바 1.4 및 그 전에 나온 버전의 JVM 중에는 volatile 키워드를 사용하더라도 동기화가 잘 안되는 것이 많다.



5. 중첩 클래스를 이용한 싱글턴 패턴


1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
    private static class SingletonHolder{
        public static final Singleton Instance = new Singleton();
    }
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        return SingletonHolder.Instance;
    }
}
 
cs


중첩 클래스를 이용한 Holder를 사용하는 방법이다.

getInstance가 호출되기 전까지 객체가 생성되지 않지만 getInstance가 호출되는 시점에 SingletonHolder가 참조되고 그때서야 Singleton 객체가 생성된다.


따라서 여기서 게으른 인스턴스 생성(lazy instantiation)도 적용되었고 synchronized 키워드도 사용하지 않았기에 비용적인 측면에서도 유리하다.


그리고 여기서 두 스레드가 동시에 getInstance를 해도 최신 VM에서 클래스를 초기화하기 위한 필드 접근은 자동으로 동기화 하기에 걱정하지 않아도 된다.




6. Enum 클래스를 이용한 싱글턴 패턴


1
2
3
4
5
6
7
public enum SingletonTest {
    INSTANCE;
  
    public static SingletonTest getInstance() {        
        return INSTANCE;
    }
}
cs


Thread-safety와 Serialization이 보장된다. 

Reflection을 통한 공격에도 안전하다. 

따라서 Enum을 이용해서 Singleton을 구현하는 것도 좋은 방법중 하나가 된다.








반응형