개념
- 클래스의 인스턴스를 단 하나만 생성하고 어디서든 그 인스턴스를 참조할 수 있도록 하는 디자인 패턴
- 정리하자면, 프로그램 시작부터 종료 시까지 어떤 클래스의 인스턴스가 메모리 상에 단 하나만 존재할 수 있게 하고 이 인스턴스에 대해 어디에서나 접근할 수 있도록 하는 패턴이다.
장점
- 고정된 메모리 영역을 얻으면서 한 번의 new로 인스턴스를 사용하기 때문에 메모리 낭비를 방지할 수 있다.
- 싱글톤으로 만들어진 클래스의 인스턴스는 전역이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉽다.
단점
- 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유시킬 경우에 다른 클래스의 인스턴스들 간에 결합도가 높아져 OCP(개방-폐쇄 원칙)를 위배하게 된다.
- 이는 객체 지향 설계 원칙에 어긋나기 때문에 수정이 어려워지고 유지보수 비용이 높아질 수 있다.
- 멀티쓰레드 환경에서 동기화 처리를 안하면 인스턴스가 2개가 생성될 수 있는 가능성이 생긴다.
활용 상황
- 공통된 객체를 여러 개 생성해야 하는 상황
- 전역에서 사용될 하나의 객체를 만들어야 하는 상황
- ex) DBCP(DataBase Connection Pool), 로거(Logger)
구현
- 싱글톤을 구현하는 6가지 방법이 있다. 1번부터 시작해서 차례대로 단점을 보완하는 방법이라고 생각하면 된다.
- Eager initialization
- Static block initialization
- Lazy initialization
- Thread safe initializaion
- Double-Checked Locking
- Bill Pugh Soluton
1. Eager initialization
- Eager initialization은 가장 간단한 형태의 구현 방법이다.
- 이는 싱글톤 클래스의 인스턴스를 클래스 로딩 단계에서 생성하는 방법이다.
- 이른 초기화라고 한다.
- 필드에 자기 자신 인스턴스를 가지면서 바로 초기화 시킨다.
- 이렇게 하게되면 빠르다는 장점이 있지만 큰 단점이 있다.
- static을 로딩하면서 바로 메모리를 가지게 된다.
- 애플리케이션에서 해당 인스턴스를 사용하지 않더라도 인스턴스를 생성하기 때문에 낭비가 발생할 수 있다.
- Exception Handling 예외 처리를 할 수 있는 방법이 없다.
2. Static block initialization
- Eager Initialization과 유사하지만 static block을 통해서 Exception Handling에 대한 옵션을 제공합니다.
- 위와 같이 구현할 경우 싱글톤 클래스의 인스턴스를 생성할 때 발생할 수 있는 예외에 대한 처리를 할 수 있지만, Eager initialization과 마찬가지로 클래스 로딩 단계에서 인스턴스를 생성하기 때문에 여전히 단점을 가지고 있다.
- 인스턴스가 static block 내에서 생성된다.
3. Lazy initialization
- 이름에 걸맞게, 앞선 두 방식과는 달리 나중에 초기화하는 방법
- global access 한
getInstance()
메서드를 호출할 때에 인스턴스가 없다면 생성한다.
getInstance()
호출 이외에는 인스턴스를 생성하지 않는다.
- 1, 2번에서 안고 있던 문제(사용하지 않았을 경우에는 인스턴스가 낭비)에 대해 어느정도 해결책이 된다.
- 그러나 멀티쓰레드 환경에서 동기화 문제가 발생한다.
- 만약 인스턴스가 생성되지 않은 시점에서 여러 쓰레드가 동시에
getInstance()
를 호출한다면 수많은 인스턴스가 생성되어 싱글톤 패턴에 위반하는 문제점이 야기될 수 있기 때문에 thread-safe 하지 않다.
4. Thread safe initializaion
- 3번의 문제를 해결하기 위한 방법으로,
getInstance()
메서드에 synchronized
를 걸어두는 방식이다.
synchronized
키워드는 임계 영역(Critical Section)을 형성해 해당 영역에 오직 하나의 쓰레드만 접근 가능하게 해준다.
- 위와 같은 방식으로 구현한다면
getInstance()
메서드에 진입하는 쓰레드가 하나로 보장받기 때문에 멀티 쓰레드 환경에서도 동작하게 된다.
- 그러나
synchronized
자체에 대한 비용이 크기 때문에 싱글톤 인스턴스 호출이 잦은 애플리케이션에서는 느리고 성능이 떨어지게 된다.
5. Double-Checked Locking
- 4번의 문제점을 해결하기 위해 고안된 방식
getInstance()
메소드 수준에 lock을 걸지 않고 instance가 null일 경우에만 synchronized
가 동작하도록 한다.
- Null 체크를 synchronized 블록 밖에서 한 번, 안에서 한 번, 총 두 번 실행
- 밖에서 하는 체크는 인스턴스가 있는 경우 빠르게 리턴하기 위해서
- 안쪽에서 하는 체크는 인스턴스가 생성되지 않는 경우 하나의 인스턴스만 생성하기 위해
6. Bill Pugh Solution
- Doublc-Checked Locking 방법이
synchronized
의 성능 저하를 보안할 수는 있지만 synchronized
를 치우고싶고 Indent가 마음에 들지 않기 때문에 Bill Pugh 라는 사람이 고안한 방식이다.
- inner static helper class를 사용하는 방식이다.
- SingletonHelper 클래스는 BillPughSingleton 클래스가 Load 될 때도 Load 되지 않다가
getInstance()
가 호출됐을 때 비로소 JVM 메모리에 로드되고 인스턴스를 생성하게 된다.
- Thread safe, Lazy Loading이 가능
- 구현 간단
참조
https://devmoony.tistory.com/43
https://dailyheumsi.tistory.com/149?category=855210
https://readystory.tistory.com/116
https://www.youtube.com/watch?v=C6CczyrkYXU
https://yaboong.github.io/design-pattern/2018/09/28/thread-safe-singleton-patterns/
https://kdhyo98.tistory.com/70?category=971166