싱글톤이란 무엇일까?
싱글톤 패턴이란, 오직 하나의 객체만을 생성할 수 있는 클래스를 말한다.
언제든 객체의 유일성을 보장할 수 있으며, public static 객체클래스 getInstance() 로 해당 객체를 어디서든 불러와
동일한 객체로 동작할 수 있게 해준다.
코드의 구조는 어떨까?
코드는 이렇게, private으로 생성자를 두고, public static 클래스 getInstance로 클래스를 불러와 사용하게 해준다.
이렇게 되어있을 때, getInstance를 부를 때 클래스를 초기화 시키는 방식으로 '지연 초기화'된다.
지연 로딩은 이렇게 있는 구조와 동일하게 여겨진다. 이렇게 하면, 명시적으로 null값이면 초기화 시켜서 인스턴스를 반환하게 해준다.
이 때 문제는, 멀티 쓰레드일 때 생길 수 있는데, 쓰레드 A, B가 있다고 생각해보자.
- A, B가 둘 다 Latte가 초기화된 적 없는 상태에서 getInstance를 호출했다. (A가 먼저일 때)
- A가 getInstance를 위해 Latte를 초기화 시킨다
- B가 getInstance를 위해 Latte를 초기화 시킨다.
위에서 말하는 '객체의 유일성을 보장한다' 라는 명제에 오류가 발생했다!
이를 해결하기 위한 방안을 만들어보자.
이렇게 static 다음에 synchronized를 하나 붙여두었다.
이렇게 하면, A가 초기화 시킨 후 다른 스레드들이 데이터에 접근하는 것을 막는다고 한다.
문제는 모든 쓰레드에서 해당 메서드를 동기화시켜주어야 한다는 단점이 있다.
이걸 해결하기 위해서, 로직을 조금 수정해야 한다.
이런식으로 코드를 조금 수정했다. 이를 이해하기 위해서는, volatile에 대해서 공부를 해야했다.
- volatile : 해당 변수 키워드가 붙어있으면, 그 값을 메인 메모리에 저장하게 해준다. CPU의 캐시 메모리에 있을 경우, 메모리로 가지 않고 해당 값을 가져온다. 이런 면에서 느려질 수 있지만, 동기화/초기화를 반복하는 것보단 수월하다고 생각된다. 이를 통해 불러오는 입장에서도 메인 메모리에 있는 데이터를 불러오게 할 수 있다고 생각된다.
위의 방식도 문제가 있다. 지금, getInstance를 위해 불러온 메서드 개수를 생각해보면, 너무 장황하다(물론 indent를 최대한 줄이기 위해 이렇게 만들긴 했다)
이를 holder클래스를 이용해 수정할 수 있다.
이 구조에서 어떻게 동작할까?
일단, Latte에서 getInstance를 불러오면 HOLDER를 불러온다. 이 때 Latte가 초기화되지 않아있다면 Latte를 초기화시킨다.
이를 통해 생성된 Latte는 초기화가 되어있으므로, 이후에 부를 때에는 초기화가 일어나지 않는다.
즉시 초기화의 구조를 가지게 된다.
대안은 없을까?
사실 위의 코드들 처럼, 상수형으로만 되어있다면, enum클래스를 쓰는 것이 더 현명한 방법이다.
이렇게 하면 싱글톤 패턴으로 모든 커피들을 종류별로 만들 필요도 없게 된다. 얼마나 편해?
대신 클래스를 참조하여 사용할 수는 없다.
단점은 무엇일까?
- TDD가 어렵다.
- 싱글톤 패턴은 결국 정적 필드를 만드는 방식이다. 이는 다른 테스트를 할 때에도 호출할 수 있고, 일반적으로 인터페이스가 아닌 클래스를 통해 구현되는 싱글톤 클래스는 mockito로 대체할 수 없으므로 단위 테스트가 힘들어진다. 이를 해결하기 위해서 주로 의존성 주입을 사용할 수 있다. 이 때 의존성 주입 프레임워크가 싱글톤 객체 생성을 제한한다.
- 멀티쓰레드에서 문제가 생길 수 있다.
- 싱글톤에서는 어디서든 똑같은 객체로 동작할 수 있어야 한다. 그렇다면, 싱글톤으로 전역필드에 존재하는 객체는 다른 쓰레드에서 부를 때, 공유되는 변수는 동기화가 되어야 한다. 이에 대한 조심을 해야하며, 그렇지 않으면 누가 맞는 값일지 달라질 수 있다.
- 싱글톤 패턴의 클래스에 의존해있을 경우, 변경에 취약해질 수 있다.
'CS' 카테고리의 다른 글
[CS] 디자인 패턴 - 옵저버 ( 야 야 너 뭐하냐? ) (1) | 2023.05.09 |
---|---|
[CS] 디자인 패턴 - 전략 패턴 (0) | 2023.04.30 |
[CS] 디자인 패턴 - 팩토리 (0) | 2023.04.21 |