티스토리 뷰
목차
Singleton Pattern
싱글톤 패턴은 자바에서 이용되는 디자인 패턴 중 하나다(스프링 부트가 아니다!).
주로 아래와 같은 상황을 해결하기 위해 사용되며,
- 공유자원 관리
디비 연결, 로깅, 캐싱과 같은 자원은 시스템 전체에서 한 번만 생성되면 충분하다. - 비용이 많이 드는 객체 생성
같은 객체가 빈번하게 생성되는 경우 비용이 많이 든다. 이 경우 싱글톤 패턴을 이용해 하나의 인스턴스를
생성한 뒤에 해당 인스턴스를 재사용함으로써 리소스 낭비를 줄일 수 있다. - 전역 변수 사용 제한
전역 변수는 시스템 복잡도를 증가시키고 지역 변수와 충돌할 가능성도 있다.
이를 해결하기 위해 싱글톤 패턴을 이용해 전역변수의 사용을 최소화할 수 있다.
보통 아래와 같은 방식으로 구현하게 된다.
- 생성자를 private으로 선언해 외부의 접근을 막는다.
- 클래스 내부에서 자신의 인스턴스를 private static으로 유일하게 선언한다.
- 유일한 인스턴스를 반환하는 getInstance() 메서드를 public static으로 선언하여 외부 접근을 허용한다.
해당 메서드는 클래스의 인스턴스를 반환하며 인스턴스가 없는 경우 생성해 준다. - 멀티스레드 환경의 문제를 막기 위해 synchronized 키워드를 사용한다.
- 직렬화가 필요하다면 Serializable 인터페이스를 구현한다.
이렇게 구현된 싱글톤 패턴 클래스는 대략 아래와 같은 모습을 가진다.
public class Singleton {
private static Singleton instance;
private Singleton() {
// 외부 객체 생성 방지를 위해 private 설정
}
public static synchronized Singleton getInstance() { // 멀티스레딩을 위한 synchronized 키워드
if(instance == null) {
instance = new Singleton(); // 인스턴스가 없으면 만들어준다.
}
return instance;
}
}
하지만 여기서 synchronized 키워드가 문제가 되는데,
해당 키워드는 메서드에 대한 동기화를 보장하기 위한 처리 때문에 약 10배에서 100배 정도 성능 저하가 발생할 수 있다.
물론 최근의 JVM은 계속해서 해당 키워드를 최적화하고 개선하려는 노력을 하고 있기는 하지만,
그대로 느리다.
따라서 synchronized 키워드의 사용을 최대한 줄이면서도 멀티스레드 환경을 대비할 필요성이 생긴다.
Double-Checked Locking
이때 필요한 것이 바로 <Double-Checked Locking> 방식이다.
이 방식은 이름대로 인스턴스의 생성 여부를 더블체크하는 방식이며, 인스턴스가 생성되지 않았을 경우에만
synchronized 블록에 들어가 객체를 생성하고, 그렇지 않을 경우 바로 객체를 반환한다.
코드로 보면 아래와 같다.
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
Singleton result = instance;
if (result == null) { // result가 비어있는지 체크
synchronized (Singleton.class) {
if (instance == null) { // 인스턴스 생성여부 체크
instance = new Singleton(); // 없다면 생성
}
result = instance; // 있다면 바로 반환
}
}
return result;
}
}
여기서 보면 인스턴스 앞에 volatile이라는 키워드가 붙은 것을 확인할 수 있다.
이 키워드는 직역하면 '휘발성의'라는 뜻이며, 해당 키워드가 붙은 변수의 최적화를 막는다.
최적화를 막는다고 하니 이상하게 들릴 수도 있겠지만, 여기선 인스턴스에 대한 캐싱을 막아 언제나 최신 값을 제공하도록
보장하는 역할을 한다. 만약 위 키워드를 사용하지 않으면 대기 중인 작업이 잘못된 값을 반영하거나
무한루프에 빠질 가능성이 있다고 한다.
Initialization-On-Demand Holder Idiom
이어서 또 다른 방법으로는 <Initialization-On-Demand Holder Idiom>이라는 패턴이 존재하는데,
이는 싱글톤 인스턴스를 클래스 로딩 시점에 미리 생성하지 않고 필요할 때 생성하는(On-Demand) 패턴이다.
우선 코드를 보자.
public class Singleton {
private Singleton() {
}
private static class LazyHolder {
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
먼저 LazyHolder라는 내부 클래스를 정의해 싱글톤을 초기화시켰다.
이 방식은 위에서 설명했듯 클래스 로딩 시점의 인스턴스 생성을 막는 역할을 한다.
그 이유는 내부 클래스 자체의 특성 때문인데, 내부클래스는 클래스 로딩 시점이 아닌 클래스가 최초로 사용될 때 초기화 된다.
때문에 서로 다른 스레드에서 동시에 인스턴스를 호출해도 LazyHolder의 초기화는 한 번만 이루어지며,
이는 다중 스레드에서 싱글톤 패턴을 안전하게, 그러면서도 불필요한 동기화 없이 수행할 수 있도록 한다.
Pros and Cons
앞서 싱글톤 패턴은 스프링부트의 패턴이 아니라는 것을 언급했다.
그 이유는 스프링 프레임워크에서는 객체의 라이프사이클을 의존성 주입을 통해 관리하기 때문이다.
이때 모든 빈(Bean) 객체를 싱글톤으로 생성해 주입하기 때문에 싱글톤 패턴을 대체하기에 충분하며,
따라서 개발자는 싱글톤 패턴보다 의존성 주입 패턴을 더 많이 사용하게 된다.
마지막으로 싱글톤 패턴의 장단점에 대해 정리하고 글을 마치자.
- 장점
- 단 하나의 인스턴스가 보장된다. 이는 다중 스레드 환경뿐 아니라 리소스 관리에도 이점이 있다.
- 생성된 인스턴스는 프로그램 전역에서 접근 및 사용이 가능하다
- 단점
- 객체지향의 원칙 중 하나인 단일 책임 원칙을 위반할 수 있다.
하나의 인스턴스가 여러 개의 문제를 해결할 가능성이 있기 때문이다. - 생성된 인스턴스에 문제가 생기면 전체 시스템이 영향을 받을 수 있다.
- 테스트가 어렵다. 싱글톤 객체가 다른 객체에 대한 의존성을 가진 경우 유닛 테스트가 어려워진다.
- 객체지향의 원칙 중 하나인 단일 책임 원칙을 위반할 수 있다.
따라서 앞서 말한 전역 변수 문제라거나 공유자원 관리 등 반드시 필요한 상황에만 싱글톤 패턴을 사용하고
특별한 경우가 아니라면 의존성 주입과 같은 패턴을 고려하는 것이 필요하다.
'Java+Spring > Java' 카테고리의 다른 글
[Java]Java17, System.err.println() (0) | 2023.06.11 |
---|---|
[Java]가변 인자(varargs) (0) | 2023.05.21 |
[Java]전략패턴 (2) | 2023.05.18 |
[Java]Map.Entry 인터페이스 (0) | 2023.03.29 |
[Java]컬렉션 프레임워크에서 제공하는 합집합, 교집합, 차집합, 부분집합 (0) | 2023.01.29 |
[Java]동시성(Concurrency) vs. 병렬성(Parallelism) (2) | 2022.10.19 |
- Total
- Today
- Yesterday
- 알고리즘
- 리스트
- spring
- 세계여행
- 지지
- 동적계획법
- 남미
- java
- 중남미
- 스트림
- 면접 준비
- 세계일주
- 유럽여행
- 자바
- 기술면접
- 유럽
- 여행
- 맛집
- RX100M5
- 파이썬
- Python
- BOJ
- 세모
- 스프링
- Backjoon
- 백준
- 칼이사
- Algorithm
- a6000
- 야경
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |