-
디자인 패턴 (6) - 싱글톤 패턴언어/디자인패턴 2023. 9. 11. 16:23
1. 싱글톤 패턴이란?
- 단 하나의 유일한 객체를 만들기 위한 코드 패턴
- 메모리를 절약하기 위해 인스턴스 필요시 새로 만들지 않고 기존의 인스턴스 가져와 활용하는 기법
- 보통 싱글톤 패턴이 적용된 객체가 필요한 경우는 리소스를 많이 차지하는 역할을 하는 무거운 클래스들
ex 데이터 연결 모듈 (스레드풀,커넥션 풀 등등)
1.1 싱글톤 패턴 구현 원리
- 생성자에 private를 붙여주기만하면된다.
- 전역변수로 미리 생성된 객체를 얻는다.
1.2 여러가지 싱글톤 패턴 구현 방법
- Eager Initialization
> 가장 직관적이면서 심플한 방법
> static final이라 멀티 쓰레드 환경에서도 안전하다.
> static멤버는 당장 객체 사용하지 않더라도 메모리에 적재하기 때문에 리소스가 큰 객체라면 공간 자원 낭비 발생
> 예외처리 어렵다
class Singleton{ private static final Singleton INSTANCE = new Singleton(); //생성자를 private선언 private Singleton(){} public static Singleton getInstance(){ return INSTANCE;} }
- Static block initialization
> static block를 이용해 예외를 잡을 수 있게됨
> 여전히 static이므로, 공간차지 심하다.
class Singleton{ private static Singleton instance; private Singleton(){} //static 블록을 이용한 예외처리 static{ try{ instance = new Singleton(); }catch(Exception e){ throw new RuntimeException("싱글톤 객체 생성 오류"); } } public static Singleton getInstance(){ retunr instance; } }
- Lazy initialization (지연 생성)
> 객체 생성 관리 내부적 처리
> 메서드 호출했을 때 인스턴스 변수의 null 유무에 따라 초기화하거나 있는걸 반환
> 미사용 고정 메모리 차지의 한계점 극복
> 쓰레드 세이프하지 않다는 치명적 단점
class Singleton{ private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
* 스레드 A,B가 동시에 생성시 A와 B가 동시에 if문을 평가하고, 이에 따라 아직 객체가 만들어지지 않았다고 평가하면
초기화 작업을 두번할 수도 있다.
- Thread safe initialization
- synchronized키워드를 통해 Thread-safe하도록 설정
- 하지만, 매번 객체를 가져올 때 , synchronized 메서드를 호출해야해서 성능 하락 발생할 수 있다.
class Singleton{ private static Singleton instance; private Singleton(){} public static synchroized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
- Double-Checked Locking
> 매번 synchronized 메서드를 실행하는 것이 문제라면 -> 최초 초기화시에만 적용하고
> 이미 만들어진 인스턴스 반환시에는 사용 x 기법
> 이때 인스턴스 필드에 volatile 키워드 붙여줘야 I/O불일치 해결가능
> but volatile키워드 사용을 위해선 JVM 1.5이상 -> JVM에 따라 여전히 쓰레드 세이프 하지 않아!
* volatile키워드
> Java에서 쓰레드 여러개 사용시 성능을 위해 각각의 쓰레드들은 변수를 메인 RAM으로부터 가져오는게 아님
> 캐시(Cache) 메모리에서 가져옴
> 문제는 비동기로 변수값 캐시에 저장했다가, 각 쓰레드마다 할당되어 있는 캐시 메모리 변수 값이 불일치할 경우가 있다.
> 그래서 volatile 키워드를 통해 이 변수는 캐시에서 읽지말고, 메인 메모리에서 읽도록 지정해줌
class Singleton{ private static volatile Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ //메서드에 동기화 x Singleton에 동기화 if(instance == null){ instance = new Singleton(); // 최초 초기화 동기화 작업이 일어남 } } } return instance; // 최초 초기화되면 앞으로 생성된 인스턴스만 반환 } }
- Bill Pugh Solution(LazyHolder)
> 권장되는 방법 중 하나
> 멀티스레드 안전, Lazy Loading도 완경
> 클래스 안에 내부 클래스를 두어 JVM 클래스 로더 매커니즘과 클래스가 로드되는 시점을 이용한 방법
> static 메소드에서는 static 멤버만 호출 가능함 -> 내부 클래스를 static으로 설정
> 다만 클라이언트가 임의로 싱글톤 파괴가능 ->(직렬화/역질렬화, Reflection API)
class Singleton{ private Singleton(){} //static 내부 클래스 사용 // Holder로 만들어서 클래스가 메모리 로드 안됨 -> getInstance메서드 호출되야 로드됨 private static class SingleInstanceHolder{ private static final Singleton INSTANCE = new Sington(); } public static Singleton getInstance(){ return SingleInstanceHolder.INSTANCE; } }
> 내부 static 클래스로 선언 -> 싱글톤 클래스 초기화 시에도 메모리에 로드x
> 어떤 모듈에서 getInstance() 호출시 SingleInstanceHolder 내부의 static멤버 가져와서 리턴
(이때 내부 클래스 한번 초기화)
> final로 지정하여 다시 값이 할당되지 않도록 방지
-Enum사용
>권장되는 두가지 방법 중 하나이다.
> enum은 애초에 멤버 만들 때 private로 한번만 만들고 초기화해서 thread safe함
> 클라이언트 Reflection 공격에 안전!
> 싱글톤 클래스를 멀티톤(일반 클래스)로 바꿔야할때 .. 다시 만들어야함
> 클래스 상속이 필요할 때 enum은 상속 불가
enum SingletonEnum{ INSTANCE; private final Client dbClient; SingletonEnum(){ dbClient = Database.getClient(); } public static SingletonEnum getInstance(){ return INSTACNCE; } public Client getClient{ return dbClient; } }
* Enum과 Bill pugh Solution기법 중 하나 사용하자
> 성능이 중시 되면: Bill, 직렬화 안정성 중시되면 : Enum
2. 싱글톤은 안티패턴인가?
- 싱글톤은 다음과 같은 문제점이 있다. 메모리 이점과 다음의 문제점간 상충관계를 잘 고려하자
1. 모듈간 의존성이 높아진다.
> 클래스의 객체를 미리 생성해서 정적 메소드를 이용해 생성 -> 클래스 사이 강한 의존성과 높은 결합 생성됨
> 하나의 싱글톤 클래스를 여러 모듈이 공유 --> 싱글톤 인스턴스 변화시 이를 참조하는 모든 모듈 수정해야함
> 클라이언트 코드가 너무 많은 곳에서 사용하면, 결합도가 너무 높아짐
2. SOLID 위배 사례가 많다.
- 싱글톤 자체가 하나만 생성됨 따라서 여러 책임 지니는 경우 많다 (SRP 위배)
- 한 클래스에 너무 많은 책임이 몰리면, 클래스간 결합도가 높아짐-> 클라이언트가 구현 클래스에 의존 -> OCP 위배
- 위의 결과 DIP도 위반
3. TDD 단위 테스트가 어렵다.
- 싱글톤 클래스를 사용하는 모듈은 테스트가 어렵다.
참고자료:
'언어 > 디자인패턴' 카테고리의 다른 글
디자인패턴 (8) - 어댑터 패턴 (0) 2023.09.12 디자인 패턴 (7) - 커맨드 패턴 (0) 2023.09.12 디자인 패턴 (5) - 추상 팩토리 패턴 (0) 2023.09.11 디자인 패턴 (4) - 팩토리 패턴 (0) 2023.09.11 디자인패턴 (3) - 데코레이터패턴 (0) 2023.09.08