안녕하세요. 개발자 Jindory입니다.
오늘은 Singletone Pattern에 대해서 글을 작성해보려고 합니다.
[ 글 작성 이유 ]
Spring Framework를 공부하다가 Singletone pattern에 대한 내용이 나와 정리해보고 싶어서 작성하게 되었습니다.
싱글톤 패턴(Singletone Pattern)이란?
싱글톤 패턴이란 어떤 객체를 어플리케이션 내에서 오직 1개만 객체가 생성되도록 강제하는 패턴입니다. 이 패턴은 프로그램 전체에서 공유되고 유일하게 존재해야하는 객체를 구현할때 유용하게 사용됩니다.
우리가 스마트폰을 사용할 때, 밝기, 음량, 와이파이 등을 설정할 수 있습니다. 해당 설정은 Instagram, Youtube, 일정 앱을 사용할 때 동일하게 적용되어 사용됩니다.
개발에서도 프로그램의 상태를 기록하고 추적하는데 로깅을 사용합니다. 로깅은 프로그램의 상태를 기록 및 추적하기 위해 어디에서든 사용되어야 하며, 객체가 일관성 있는 작업을 해야하기 때문에 싱글톤 패턴으로 구현해야하는 객체 중 하나라고 할 수 있습니다. 또한 로깅은 자주 호출되는 객체이기 때문에 각각의 프로그램에서 각각의 로깅 객체를 생성하는것 보다 하나의 객체를 공유하는것이 메모리 낭비와 성능 저하를 막을 수 있습니다.
싱글톤 패턴(Singletone Pattern) 사용법
싱글톤 패턴은 위의 정의에서 설명한 바와 같이 하나의 객체만 만들어 지도록 제한하고, 프로그램 전역에서 사용할 수 있도록 구현해야 합니다.
간단하게 싱글톤 패턴을 구현하는 방법은 아래와 같이 정리해 볼 수 있습니다.
- 클래스의 생성자를 private로 선언하여 다른 객체가 인스턴스화 할 수 없게 합니다.
- 인스턴스에 대한 참조를 반환하는 static 메소드를 제공합니다.
- 객체 생성시 객체가 존재하는지 확인하고, 없으면 생성하고, 있으면 기존의 것을 반환하도록 구현합니다.
위 규칙에 따라서 싱글톤 패턴을 구현하는 기법은 몇가지가 있어서, 그 기법에 대해서 하나씩 정리해 보도록 하겠습니다.
Eager Initialization(즉시 초기화)
Eager Initialization 기법은 구현한 클래스 호출과 동시에 인스턴스를 생성하는 방법입니다. 가장 간단한 방법이지만, 인스턴스가 사용되지 않아도 될 경우에도 인스턴스를 생성하여 메모리 낭비가 발생할 수 있습니다.
Eager Initialization 기법으로 구현한 객체는 아래와 같습니다.
public class Logger {
// 싱글톤 인스턴스를 저장할 static 변수
private static Logger instance = new Logger();
// 생성자를 private로 선언하여 외부에서 인스턴스화를 막음
private Logger() {}
// 인스턴스를 반환하는 static 메소드
public static Logger getInstance() {
// 인스턴스가 존재하지 않으면 새로 생성
if (instance == null) {
instance = new Logger();
}
// 인스턴스를 반환
return instance;
}
// 로깅 기능을 수행하는 메소드
public void log(String message) {
// 로깅 로직
}
}
Lazy Initialization(지연 초기화)
다음은 객체를 사용하는 시점에 생성하는 Lazy initialization 기법입니다.
실제로 인스턴스를 사용하지 않으면 생성하지 않아도 되기 때문에, Eager Initialization보다 효율성이 좋지만, 멀티 스레드 환경에서 두개 이상의 스레드가 동시에 싱글톤 인스턴스에 접근시 동시에 객체를 생성할 수 있으므로 동기화 문제가 발생할 수 있습니다.
Lazy Initialization 기법으로 구현한 객체는 아래와 같습니다.
public class Logger {
// 인스턴스를 저장할 static 변수
private static Logger instance;
// 생성자를 private로 선언하여 외부에서 인스턴스화를 막음
private Logger() {}
// 인스턴스를 반환하는 static 메소드
public static Logger getInstance() {
// 인스턴스가 존재하지 않으면 새로 생성
if (instance == null) {
instance = new Logger();
}
// 인스턴스를 반환
return instance;
}
// 로깅 기능을 수행하는 메소드
public void log(String message) {
// 로깅 로직
}
}
Thread Safe Initialization(쓰레드 세이프 초기화)
다음으로 멀티 스레드 환경에서 Lazy Initialization으로 구현시 다수의 Thread에 의해 중복으로 생성될 수 있는 문제를 보완한 Thread Safe Initialization 기법입니다.
Lazy Initialization과 비슷하지만, 인스턴스를 반환하는 getInstance() 메서드에 synchronized 키워드를 사용하여 동기화를 보장합니다. 아래와 같이 구현함으로써 동기화 문제를 해결할 수 있지만, 동기화 처리로 인한 성능 저하가 발생할 수 있습니다.
Thread Safe Initialization 기법으로 구현한 객체는 아래와 같습니다.
public class Logger {
// 인스턴스를 저장할 static 변수
private static Logger instance;
// 생성자를 private로 선언하여 외부에서 인스턴스화를 막음
private Logger() {}
// 인스턴스를 반환하는 static 메소드
public static synchronized Logger getInstance() {
// 인스턴스가 존재하지 않으면 새로 생성
if (instance == null) {
instance = new Logger();
}
// 인스턴스를 반환
return instance;
}
// 로깅 기능을 수행하는 메소드
public void log(String message) {
// 로깅 로직
}
}
Double-Checked Locking(이중 잠금)
인스턴스를 필요할 때 생성하고, static 변수에 저장하는 Double-Checked Locking 방법입니다.
Thread Safe Initialization과 비슷하지만, 인스턴스가 null인 경우에만 synchronized 블록을 실행하여 동기화를 수행합니다. 성능 저하를 줄일 수 있지만, CPU 멀티코어 문제로 인해 잘못된 결과를 반환할 수 있습니다.
(위에서 설명한 잘못된 결과에 대해서는 여기를 참조하시는게 좋을것 같습니다.)
Double-Checked Initialization 기법으로 구현한 객체는 아래와 같습니다.
public class Logger {
// 인스턴스를 저장할 static 변수
private static volatile Logger instance;
// 생성자를 private로 선언하여 외부에서 인스턴스화를 막음
private Logger() {}
// 인스턴스를 반환하는 static 메소드
public static Logger getInstance() {
// 인스턴스가 존재하지 않으면 동기화 블록 실행
if (instance == null) {
synchronized (Logger.class) {
// 다시 한번 인스턴스가 존재하지 않으면 새로 생성
if (instance == null) {
instance = new Logger();
}
}
}
// 인스턴스를 반환
return instance;
}
// 로깅 기능을 수행하는 메소드
public void log(String message) {
// 로깅 로직
}
}
LazyHolder 패턴
인스턴스를 필요할 때 생성하고, static 변수에 저장하는 방법입니다.
내부 클래스를 사용하여 인스턴스를 생성하고, 클래스 로딩 시점에 초기화 되도록 합니다. JVM의 클래스 로딩 메커니즘을 이용하여 동기화 문제와 성능 저하 문제를 해결 할 수 있는 구현 기법입니다.
Bill Plugh Solution 기법으로 구현한 객체는 아래와 같습니다.
public class Logger {
// 생성자를 private로 선언하여 외부에서 인스턴스화를 막음
private Logger() {}
// 인스턴스를 반환하는 static 메소드
public static Logger getInstance() {
// 내부 클래스의 인스턴스를 반환
return LazyHolder.INSTANCE;
}
// 로깅 기능을 수행하는 메소드
public void log(String message) {
// 로깅 로직
}
// 내부 클래스를 정의
private static class LazyHolder {
// 인스턴스를 생성하고, static 변수에 저장
private static final Logger INSTANCE = new Logger();
}
}
이렇게 싱글톤 패턴으로 객체를 구현하는 기법에 대해 살펴봤습니다.
싱글톤 패턴(Singletone Pattern) 구현이 필요한 상황 및 장단점
싱글톤 패턴은 객체의 인스턴스를 하나만 생성하고 이를 공유하여 전체 프로그램에서 사용할 수 있는 구현 방법입니다. 이런 구현 기법을 사용해야하는 상황과 장단점에 대해서 설명해보도록 하겠습니다.
싱글톤 패턴 구현이 필요한 상황은 아래와 같이 정리해 볼 수 있습니다.
> 싱글톤 패턴 구현이 필요한 상황
- 프로그램 내에서 하나의 객체만 존재해야하는 경우(전체 프로그램에서 공유되고 유일하게 존재해야하는 객체)
ex) 시스템 설정, 데이터베이스 연결, 로깅 서비스 등.... - 프로그램 내에서 여러 부분에서 해당 객체를 공유하여 사용해야하는 경우.
ex) 쓰레드 풀, 캐시, 로거 등.... - 객체 생성 비용이 높거나 자주 생성되는것을 방지하고자 하는 경우
ex) 네트워크 연결, 파일 입출력 등...
위와 같은 상황에서 싱글톤 패턴이 주로 사용됨을 알 수 있습니다. 위에서 작성된 내용으로는 싱글톤 패턴이 좋은 디자인 패턴으로 보이는데 싱글톤 패턴의 장단점에 대해서 정리해 보도록 하겠습니다.
> 싱글톤 패턴의 장점
- 객체의 인스턴스가 하나만 생성되므로, 메모리를 절약하고 성능을 향상시킬 수 있습니다.
- 객체의 상태를 일관성 있게 유지하고, 전역에서 접근 가능하도록 할 수 있습니다.
- 객체 생성 비용이 높거나 객체 사용시 자주 생성되는것을 방지할 수 있습니다.
> 싱글톤 패턴의 단점
- 객체의 인스턴스가 너무 많은 역할을 수행하거나 많은 데이터를 관리하면, 복잡도와 의존성이 증가하고, 테스트와 유지보수가 어려워 질 수 있습니다.
- 싱글톤 패턴으로 구현한 객체의 인스턴스가 멀티스레드 환경에도 사용될 경우, 동기화 문제가 발생할 수 있습니다.
- 객체의 인스턴스가 전역 상태를 만들기 때문에, 객체 간의 결합도를 높이고, 모듈성과 재사용성을 낮추며, 은닉성을 해칠 수 있습니다.
싱글톤 패턴 구현시 주의할 사항
싱글톤 패턴은 위와 같은 장점을 가지고 있지만, 잘못 사용시 단점이 부각될 수 있으므로, 몇가지를 주의하여 구현을 해야합니다.
1. 인스턴스가 하나만 존재하도록 보장
싱글톤 패턴은 객체의 인스턴스가 하나만 존재하도록 보장해야하기 때문에 생성자를 private로 선언하고, 외부에서 인스턴스를 접근할 수 있도록 static 메소드로 제공해야합니다. private으로 생성자를 정의하지 않을 경우, 중복해서 객체를 생성할 수 있으므로, 반드시 private 생성자를 작성해야 합니다.(생성자를 명시적으로 선언하지 않으면, 컴파일러가 클래스의 접근 제한자와 동일한 기본 생성자(default constructor)를 자동으로 생성해 줍니다.)
2. 멀티 스레딩 환경에서 동기화 처리
싱글톤 패턴이 멀티스레드 환경에서 여러개의 쓰레드에 의해 동시에 생성자를 생성하거나 접근할 경우 문제가 발생할 수 있습니다. 따라서 적절한 동기화 기법을 사용해야합니다.
ex) 이중 확인 잠금(DCL, Double Checked Locking), Bill Pugh Solution ...
3. 상황에 맞춰 싱글톤 패턴이 필요한 경우에만 구현
싱글톤으로 구현한 객체가 역할이나 데이터가 너무 많아지면, 복잡도와 의존성이 증가하고, 테스트 및 유지보수가 어려워 질 수 있습니다. 따라서 책임을 분리하고, 필요한 경우에만 구현해야 합니다. 또한 객체 지향의 원칙과 상충될 수 있으므로, 주의해서 사용해야 합니다.
이렇게 싱글톤 패턴(Singletone Pattern)에 대해서 알아봤습니다.
혹시라도 정정할 내용이나 추가적으로 필요하신 정보가 있다면 댓글 남겨주시면 감사하겠습니다.
오늘도 Jindory 블로그에 방문해주셔서 감사합니다.
[ 참조 ]
https://coding-factory.tistory.com/709
https://brunch.co.kr/@eddwardpark/42
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sthwin&logNo=220836837262
'분석 및 설계 > Design Pattern' 카테고리의 다른 글
[설계] 객체지향 설계 5원칙(SOLID) (0) | 2024.06.05 |
---|