싱글톤 패턴
개념
인스턴스가 단 1개만 존재해야할 때 사용되는 패턴이다.
시스템 런타임이나 환경변수 등 인스턴스가 여러 개가 되면 문제가 발생하는 경우, 이러한 패턴을 이용하여 인스턴스가 단 1개만 생성되어 해당 인스턴스가 모든 곳에서 공유되어 사용되도록 한다.
구현 방법은 다양하지만, 추천되는 방법은 6번과 7번이다.
실제로 이펙티브 자바를 쓴 조슈아 블로크는 7번 방법을 가장 추천했다고 한다.
방법 1. 가장 기본적인 private + static
자세히
생성자를 private 으로 만든 이유는?
Mother class 의 인스턴스를 아무 곳에서나 마음대로 생성하지 못하도록 private 을 통해 접근 제어를 해주었다.
getInstance() 를 static 으로 선언한 이유는?
static, non-static 과 상관없이 모든 곳에서 인스턴스에 접근하여 사용할 수 있도록 하기 위해서
getInstance() 가 멀티 쓰레드 환경에서 안전하지 않은 이유는 무엇인가
쓰레드 1번이 getInstance() 에 최초로 접근하여 최초의 인스턴스가 생성되는 중에 쓰레드 2번이 같은 매소드가 동시에 접근하여 인스턴스를 다시 생성할 수 있으므로.
즉, 쓰레드 1번에 getInstance() 에 접근하는 동안, 다른 쓰레드들이 접근할 수 있기 때문이다.
문제점
웹 어플리케이션은 대부분 멀티 쓰레드 환경을 기반으로 한다. 이때, 하나의 쓰레드가 getInstance() 에 접근하는 동안 다른 쓰레드들 역시 동시에 접근할 수 있기 때문에, 자칫하면 인스턴스가 하나 이상 생성될 가능성이 있다.
방법 2. synchronized
synchronized 키워드를 붙여서 문제가 되고 있는 getInstance() 매소드를 접근하고 있는 쓰레드가 존재하는 동안에는 lock 을 걸어 접근 제어를 해버린다.
자세히
자바에서 동기화 블록의 처리 방법은?
자바에서는 synchronized 키워드를 이용하여 동기화 블록을 처리한다.
해당 키워드가 붙은 영역은 한 시점에 한 쓰레드만 접근 가능하도록 보장해주며, 이미 한 쓰레드가 점유하고 있는 동안 다른 쓰레드는 모두 blocked 상태가 된다.
synchronized 키워드를 어디에 배치시키느냐에 따라서 4가지 유형이 존재한다.
getInstance() 메소드 시 동기화 하는 lock 은 인스턴스 lock 인가, 클래스 lock 인가?
클래스 lock 이다. Mother class 전체에 대해서 접근 제한을 하고 있으므로 동기화의 대상이 클래스 전체이다. 따라서 이 메소드는 클래스 lock을 통해 동기화를 수행한다.
문제점
synchronized 로 정의된 매소드는 매번 locking 이 걸리기 때문에 성능상 이슈가 존재한다.
사실 최초로 생성되는 때에만 동시성이 문제가 되고, 그 이후로는 굳이 동시성 이슈를 신경쓰지 않아도 되는데, 이 문제를 어떻게 해결할 수 있을까?
방법 3. eager initialization
synchronized 로 인한 locking 문제를 해결하기 위해 여러가지 방법이 등장했다. 그 중 하나가 바로 이른 초기화 방법이다.
자세히
이른 초기화의 단점은 무엇인가?
하단 참고
만약 생성자에서 checked 예외를 던진다면, 코드를 어떻게 수정할 수 있겠는가
문제점
어플리케이션이 구동될 때 즉시 만들어지므로, 해당 인스턴스가 생성되기 전까지는 오랜 시간동안 쓸모없는 자원이 낭비되는 셈이다.
방법 4. static block 사용하기
방법 5. double-checked-locking
자세히
double-checked locking 이라고 불리는 이유는?
getInstance() 매소드를 살펴보면, 인스턴스가 생성되었는지 여부를 두번에 걸쳐서 확인하고 있다. 그 때문에 위와 같은 이름이 붙었다.
instance 를 필요한 시점에 만들 수 있다.
instance 변수는 어떻게 정의해야하는가?
volatile 키워드를 추가해서 정의해야한다.
메모리 구조는 메인 메모리 위에 CPU 캐시 메모리라고 불리는 L3, L2,L1 캐시가 존재한다.
java 에서는 스레드가 2개 열리면 변수를 메인메모리인 RAM 에서 불러오는 것이 아니라, 캐시 메모리에서 각각의 캐시 메모리를 기반으로 가져오게 된다.
때문에 쓰레드에서 동시에 접근하는 변수의 값은 서로 불일치할 수 있다.
이때, volatile 키워드를 사용하게 되면, 캐시메모리가 아니라 메인 메모리를 기반으로 저장하고 읽어오게 되고, 이러한 값 불일치 문제를 해결할 수 있게 된다.
문제점
volatile 키워드 추가 및 두번이나 인스턴스 생성 여부 체크 등 코드가 전체적으로 장황해진다.
방법 6. static inner class (✨recommended✨)
자세히
실제로 권장되는 방법 중 하나
이 방법으로는 멀티 쓰레드 환경에서도 동기화 문제도 해결이 가능하다.
static final 을 썼는데도 왜 지연 초기화 (lazy loading)이라고 볼 수 있는가?
실제로 getInstance() 가 호출되는 시점에 Mother5Holder 내부 클래스가 로딩이 되고, SELF 인스턴스가 초기화 되기 때문에 지연 로딩이 가능해진다.
방법 7.enum class (✨recommended✨)
자세히
리플렉션에 안전하다.
직렬화, 역직렬화에 안전하다.
enum type 의 인스턴스를 리팩토링을 만들 수 있는가
enum type 으로 싱글톤을 구현할 때의 문제점은 무엇인가
클래스 로드시에 미리 만들어지기 때문에 사용 시점 이전까지는 필요없는 자원이 낭비되는 꼴이다.
상속을 쓰지 못한다.
직렬화와 역직렬화 시에도 별도로 구현해야하는 매소드가 존재하는가
없다. Enum 클래스 자체가 이미 serializable 을 구현하고 있기 때문에 신경쓰지 않아도 된다.
권장하는 패턴 2가지
static inner class 방식의 6번과 enum 을 이용한 7번방법이 권장된다.
싱글톤을 깨는 방법들
1. 리플렉션 이용하기
리플렉션이란
구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바의 API 이다.
setAccessible(true) 를 사용하는 이유는?
2. 직렬화, 역직렬화 이용하기
자바의 직렬화롸 역직렬화에 대해서 설명해보기
SerializableId 란 무엇인가
try-resource 블럭에 대해 설명해보자.
그렇다면, 실제로 싱글톤 패턴은 어떻게 사용되는가...? 그것이 알고싶다...!!!!
Java 에서의 사용 예시
Java 의 java.lang.Runtime
Spring 에서의 사용 예시
Spring 에서 bean 의 스코프 중에서 싱글톤 스코프
스프링 빈으로 등록되면, ApplicationContext 내부에서 유일한 인스턴스로 존재하게 된다.
다른 예시
다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리 등) 구현체의 일부로 쓰이기도 한다.
데이터베이스 연결모듈에도 많이 쓰인다.
복습하기
java 에서 enum 을 사용하지 않고 싱글톤 패턴을 구현하는 방법은 무엇인가
private constructor + public static method
synchronized
eagar initialization
static inner class
private 생성자와 static 매소드를 사용하는 방법의 단점은
멀티 쓰레드 환경에서 하나의 인스턴스만을 보장하지 못한다.
동시에 static method 에 접근할 경우, 인스턴스가 하나 이상 생성될 위험이 존재한다.
enum 을 사용해 싱글톤 패턴을 구현하는 방법의 장점과 단점은
우선 리플렉션이나 직렬화 등의 방법으로도 절대 깨지지 않는 싱글톤을 만들 수 있고
코드도 간결하다는 장점이 있다.
하지만 상속을 사용하지 못한다는 점과
클래스 로드시에 미리 생성된다는 점은 여전히 단점으로 존재한다.
Last updated