싱글턴(Singleton)이란?
생성자가 여러번 호출되더라도 실제로 생성되는 객체는 하나인 디자인패턴이다. 즉, 객체의 인스턴스가 오직 1개만 생성되는 패턴이다. 따라서, 어플리케이션 내에서 전역적으로 접근이 가능하다.
주의할점
상태를 가진 객체를 Singleton으로 만들면 안된다.
예를 들어, 멀티 스레드 환경에서 각기 다른 스레드가 객체의 상태를 변경시킬 가능성이 있기 때문이다.
싱글톤 패턴을 사용하는 이유?
한번의 객체 생성으로 재사용이 가능하기 때문에 메모리의 낭비를 방지할 수 있다. 주로 공통된 객체를 여러개 생성해서 사용하는 DB의 Connection Pool 에서 사용한다.
즉, 멀티 스레드 환경에서 싱글톤 객체를 공유하며 동시에 접하는 경우 동시성 문제도 해결해주는 패턴이다.
프로그램 내에서 어떤 객체가 한개만 존재해야한다. 프로그램 내부에서는 이 객체를 공유하며 사용한다.
싱글톤 패턴의 문제점
- Private 생성자를 갖고 있기 때문에 상속을 할 수 없다.
- 싱글톤 패턴은 자기 자신만이 오브젝트를 만들 수 있도록 제한했기 때문에 생성자 자체를 private으로 제한한다.
- private을 가진 생성자는 상속이 불가능하다. 따라서, 객체지향의 장점인 상속과 다형성을 적용할 수 없다!
- 또한, 상속과 다형성 같은 객체지향의 특징이 적용되지 않은 static 필드와 메소드를 사용하는것도 단점이다.
- 서버 환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다
- 서버에서는 클래스 로더를 어떻게 구성하고 있느냐에 따라서 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어 질 수 있다.
싱글톤 구현 패턴
1. Eager Initialization(이른 초기화, Tread-safe)
- 클래스 로더가 초기화하는 시점에서 정적 바인딩을 통해 해당 인스턴스를 메모리에 등록해서 사용하는 방식.
- 이른 초기화 방식은 클래스 로더에 의해 클래스가 최초로 로딩될때, 객체가 생성되기 때문에 Thread-Safe하다.
- 클라이언트에서 사용하지 않더라도 인스턴스가 항상 생성되기때문에 예외 처리를 할 수 있는 방법이 없다.
2. Static Block Initialization
- Eager Initialization과 유사하지만 static block을 사용해 싱글톤 클래스의 인스턴스 생성에 대한 예외 처리를 할 수 있다.
- 클래스 로더에 의해 클래스가 최로로 로딩될때 인스턴스를 생성하기 때문에, 크기가 큰 리소스들을 다룰때 부적합하다.
3. Lazy Initialization
- 이전의 두 방식과는 다르게(컴파일 시점에 인스턴스를 미리 생성함) Singleton 객체에 접근할때 인스턴스의 생성 상태를 확인하여 가져오는 방식이다.
- 멀티 스레드 환경에서 동기화 문제가 발생한다. 예를 들어, 인스턴스가 생성되지 않은 시점에서 여러 스레드가 동시에 getInstance()를 호출할때와 같은 상황이 있을 수 있다.
4. Lazy Initialization with synchronized (Thread-Safe)
- synchronized 키워드를 사용하여 임계 영역을 만든다. 이렇게 하면 해당 영역에서 오직 하나의 스레드만 접근 가능하게 해준다.
- synchronized 키워드에 대한 비용이 엄청나게 크기 때문에 싱글톤 인스턴스를 자주 사용해야 하는 경우, 성능 문제가 생길 수 있다.
5. Double Checked Locking
- getInstance() 메소드 수준에 Lock 을 걸지 않고 인스턴스가 Null 일 경우에만 synchronized 를 걸어준다.
- 밖에서 하는 Null 체크는 인스턴스가 있는 경우 빠르게 리턴하기 위해서 사용한다
- 안쪽에서 하는 Null 체크는 인스턴스가 생성되지 않은 경우 하나의 인스턴스만 생성하게끔 한다.
6. Bill Pugh Singleton Implementaion
- inner static helper Class를 사용한 방식으로, 현재 가장 널리 쓰이는 싱글톤 구현 방법이다.
- LazyHolder는 static 필드를 붙였기 때문에 메모리에 미리 할당되있는 상태다. 따라서, getInstance()를 호출하면 JVM은 LazyHolder 클래스를 로드하게 되는데 이미 메모리에 올라가 있는 상태라서 한번만 로드하게 된다. 이후 두번째 스레드가 getInstance()를 호출하더라도 JVM이 제공하는 동기화 기법에 따라 첫번째 로드가 끝나고 초기화가 완료될때까지 기다린다.
- 즉, 동기화 문제도 해결된다. Thread-Safe!
References.
https://it-mesung.tistory.com/118
'JAVA' 카테고리의 다른 글
SSAFY 친구들과 함께 한 TDD 스터디 후기 (0) | 2021.07.02 |
---|---|
Comparable , Comparator 인터페이스 차이점 (0) | 2021.02.15 |
JAVA - 10진수를 2진수, 8진수로 변환하기 (0) | 2021.02.10 |
Collection 중복체크 - equals(), hashCode() (0) | 2021.01.31 |
JVM Memory 구조 (0) | 2021.01.31 |