-
Singleton Pattern기술 면접/Design Pattern 2021. 9. 27. 16:09
Context에서 Application Context는 Singleton으로 되어있다.
Singleton Pattern은 이름에서 어느정도 알 수 있듯이 인스턴스를 하나만 만들어 사용하기 위한 패턴이다. 스레드 풀, 커넥션 풀 등에서 인스턴스를 여러 개 만들게 되면 자원을 낭비하거나 버그를 발생시킬 수 있기 때문에 이를 방지하고자 오지 하나의 인스턴스만 생성하여 사용하도록 하는 것이 이 패턴의 목적이다.
Singleton Pattern은 하나의 인스턴스를 유지하기 위해 인스턴스 생성에 특별한 제약을 걸어야 하는데, new 를 실행할 수 없도록 생성자를 private으로 접근 제어자를 지정하고, 유일한 단일 객체를 반환 할 수 있도록 정적 메소드를 제원해야 한다. 또한 유일한 개체를 참조할 정적 참조변수가 필요하다.
Singleton Pattern Default Form
보통의 경우에는 위 코드가 정상적으로 작동한다. 하지만, 서로 다른 독립적인 Thread가 거의 동시에 Singleton 객체를 생성하려 하는데 한 Thread가 new Singleton()통하여 인스턴스가 생성되기 직전 다른 Thread가 if(singleObject==null)을 통과하게 된다면 두개의 Thread가 두개의 인스턴스를 가지고 작업을 진행하게 된다.
이 문제를 해결하기 위해 몇가지 해결법이 나왔다.
Singleton Pattern 문제 해결 방법
해결법 1) synchronized 키워드 사용
synchronized 키워드는 Multi-Thread의 동시 접근을 막기 위해 사용된다. 정확하게 말하자면 Multi-Thread의 동기화를 제어하기 위해 사용된다. synchronized 키워드로 손쉽게 문제가 해결되는 것처럼 보이지만, synchronized키워드를 사용하게 된다면 성능이 100배정도 저하된다고 하니 좋은 해결방법은 아니다.
해결법 2) Double Checking Locking (a.k.a DCL) 사용
이론상으론 DCL을 써서 getSingletonObject()에서 동기화 되는 영역을 줄일 수 있다. getSingletonObject() 직접적으로 synchronized키워드를 명시하지 않음으로 써 동기화 오버헤드를 줄여보고자 만들어졌다. singletonObject가 null인지 체크하고 null경우에만 동기화 블럭에 진입하기 때문에 효율적으로 작동할 것처럼 보인다.
하지만 위 코드도 Multi-Thread에서 위험 요소가 있다. 예를 들어, Thread A와 Thread B가 존재하는데 Thread A에서 singletonObject의 생성이 완료 되기 전 singletonObject를 위한 메모리 공간이 할당되는 것이 가능하다. 즉 singletonObject의 생성이 완료되기 전에 메모리공간 할당이되면서 Thread B가 할당된 것을 보고 singletonObject를 사용하려고 하지만 생성이 완료되지 않았기 때문에 오류가 발생할 수 있다.
위 이유로 지금은 추천되지 않는 방법이다.
해결법 3) volatile, 객체를 로딩 시점에 생성
해결법 1, 2 에서 본 코드들은 getSingletonObject()라는 메소드를 호출하면 그 때서야 객체르 생성하고, 생성한 객체를 반환한다. 그에 반해 이번 코드는 로딩되는 시점에 미리 객체를 생성해두고 그 객체를 반환한다.
이렇게 하면 프로그램이 실행되고 나서 처음부터 끝까지 객체가 메모리에 있게 된다.
volatile키워드가 눈에 보인다. volatile 은 Multi-Threading 환경에서 동기화를 해주는 키워드다. 좀 더 구체적으로는 변수를 CPU 캐시가 아닌 메인 메모리에 저장시키고 읽을 때도 메인 메모리에서 읽어들인다.
즉 CPU에서 캐시에 저장하는 값과 메인 메모리에 저장된 값이 서로 다른경우를 방지하고자 캐시가 아닌 메인 메모리에 저장 하는 것이다. 따라서 Multi-Thread 환경에서 여러개의 Thread가 메인 메모리에서 값을 읽어오고, 저장하기 때문에 동기화 문제를 해결할 수 있다.
sychronized키워드는 메소드나 작업 자체에 lock을 걸어 원자화 시켜주는 반면, volatile은 변수를 최신값으로 유지시켜줌으로써 동기화를 지원한다.
해결법 4) Lazy Initialization
중첨 클래스를 이용하여 Holder를 사용하는 방법이다. getSingletonObject() 호출되기 전까지 Singleton 인스턴스는 생성되지 않는다. 그리고 getSingletonObject() 가 호출되는 시점에 SingletonHolder가 참조되고 그 때 Class가 로딩되며, 초기화가 진행된다. Class를 로딩하고, 초기화하는 시점은 thread-safe를 보장하기 때문에 volatile, synchronized 같은 키워드도 필요없다.
'기술 면접 > Design Pattern' 카테고리의 다른 글
Proxy Pattern (0) 2021.09.27