공부한것들을 정리하는 블로그 입니다.
싱글톤(Singleton) 디자인패턴 본문
싱글톤(Singleton) 디자인패턴 이란?
가끔 전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우가 있다.
단 하나만 생성된다고 해서 이 객체를 싱글톤(Singleton)이라고 한다.
싱글톤을 만들려면 클래스 외부에서 new 연산자로 생성자를 호출할 수 없도록 막아야 한다.
생성자를 호출한 만큼 객체가 생성되기 때문이다.
생성자를 외부에서 호출할 수 없도록 하려면 생성자 앞에 private 접근 제한자를 붙여주면 된다.
그리고 자신의 타입인 정적 필드를 하나 선언하고 자신의 객체를 생성해 초기화한다.
이때 정적 필드도 private 접근 제한자를 붙여 외부에서 필드값을 변경하지 못하도록 막는다.
대신 외부에서 호출할 수 있는 정적 메소드인 getInstance()를 선언하고 정적 필드에서 참조하고 있는 자신의 객체를 리턴해준다.
public class Singleton {
//정적 필드
private static Singleton instance = new Singleton();
//생성자
private Singleton() {
// 생성자는 외부에서 호출못하게 private 으로 지정해야 한다.
}
//정적 메소드
public static Singleton getInstance() {
return instance;
}
}
외부에서 객체를 얻는 유일한 방법은 getInstance() 메소드를 호출하는 방법이다.
싱글톤 패턴을 쓰는 이유와 문제점
애플리케이션이 시작될 때 어떤 클래스가 최초 한번만 메모리를 할당하고(Static) 그 메모리에 인스턴스를 만들어 사용하기 때문에
싱글톤 패턴은 단 하나의 인스턴스를 생성해 사용하는 디자인 패턴이다.
인스턴스가 필요 할 때마다 똑같은 인스턴스를 만들어 내는 것이 아니라, 기존의 동일한 인스턴스를 사용하게 된다는 것이므로 다음과 같은 장/단점이 존재한다.
싱글톤 패턴을 쓰는 이유
고정된 메모리 영역을 얻으면서 한번의 new로 인스턴스를 사용하기 때문에 메모리 낭비를 방지 할 수 있다.
또한 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉽다.
인스턴스가 절대적으로 1개만 존재하는 것을 보증 할 수 있으며
두 번째 이용시부터는 객체 로딩 시간이 현저하게 줄어 성능이 향상된다.
사용예시
DataBase Connection Pool 처럼 공통된 객체를 여러개 생성해서 사용해야 하는 상황에서 많이 사용한다.
- DataBase Connection Pool
- 쓰레드풀
- 캐시
- 대화상자
- 사용자 설정
- 레지스트리 설정
- 로그 기록 객체
싱글톤 패턴의 문제점
싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유시킬 경우, 다른 클래스의 인스턴스들 간에 결합도가 높아진다.
이는 '개방-폐쇄 원칙'을 위배하게 된다.
또한 의존 관계상 클라이언트가 구체 클래스에 의존하게 된다. new 키워드를 직접 사용하여 클래스 안에서 객체를 생성하기 때문에 이는 '의존성역전 원칙'을 위배하게 된다.
싱글톤 인스턴스가 자원을 공유하기 때문에 테스트하기 어려워진다. 독립된 환경에서의 테스트가 수행되려면 매번 인스턴스의 상태를 초기화시켜줘야 하기 때문이다.
(그렇지 않으면 애플리케이션 전역에서 상태를 공유하기 때문에 테스트가 온전하게 수행되지 못한다)
이러한 단점들 때문에 객체지향 설계 원칙에 어긋나게 되고, 이는 수정이 어렵고 테스트하기 어려워 유연성이 떨어진다는 단점을 낳는다.
이 외에도 자식클래스를 만들 수 없다는 점과, 내부 상태를 변경하기 어렵다는 점 등 여러가지 문제들이 존재한다.
특히 멀티쓰레드 환경에서 동기화처리를 안하면 인스턴스가 2개가 생성되는 경우가 발생할 수 있다.
멀티쓰레드에서 안전한(Thread-safe) 싱글톤 클래스, 인스턴스 예시
1. Thread safe Lazy initialization (게으른 초기화)
자신의 타입인 정적 필드를 하나 선언한다.
static 정적 필드에 private 접근제한자를 붙여 외부에서 필드값을 변경하지 못하도록 막는다.
대신 외부에서 호출할 수 있는 정적 메소드인 getInstance()를 선언하고 정적 필드가 null인 경우 자신의 객체를 생성 후 리턴해준다.
이때 getInstance()에 synchronized 키워드를 사용해서 thread-safe하게 만든다.
public class ThreadSafeLazyInitialization {
private static ThreadSafeLazyInitialization instance;
private ThreadSafeLazyInitialization(){}
public static synchronized ThreadSafeLazyInitialization getInstance(){
if(instance == null){
instance = new ThreadSafeLazyInitialization();
}
return instance;
}
}
하지만 synchronized 특성상 비교적 큰 성능저하가 발생하므로 권장하지 않는 방법이다.
2. Initialization on demand holder idiom (holder에 의한 초기화)
Holder 내부에 정적으로 선언된 instance는 static이기 떄문에 클래스 로딩시점에 한번만 호출될 것이며
final을 사용해 다시 값이 할당되지 않도록 만든다.
JVM의 클래스 초기화 과정에서 보장되는 원자적 특성을 이용하는 방법으로
클래스안에 클래스(Holder)를 두어 JVM의 Class loader 매커니즘과 Class가 로드되는 시점을 이용하는 방법이다.
개발자가 직접 동기화 문제에 대해 코드를 작성하는 것이 아니라 싱글톤의 초기화 문제에 대한 책임을 JVM으로 넘길 수 있다.
public class Singleton {
private Singleton() {}
private static class LazyHolder {
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
가장 많이 사용하고 일반적인 Singleton 클래스 사용 방법이다.
참고 :
https://jeong-pro.tistory.com/m/86
https://tecoble.techcourse.co.kr/post/2020-11-07-singleton/
'JAVA > 공부' 카테고리의 다른 글
JVM, GC, 자바의구동원리 (0) | 2022.05.17 |
---|---|
JVM에 대하여 정리 (0) | 2022.04.30 |
좋은 객체 지향 설계의 5가지 원칙(SOLID) (0) | 2022.04.24 |
접근제한자(Access Modifier) : public, protected, default, private (0) | 2022.04.23 |
gmail로 email 인증번호 보내기(비밀번호 찾기 기능/ 회원가입시 이메일 인증/smtp) (2) | 2019.12.13 |
추상클래스와 인터페이스 (0) | 2018.11.10 |
JAVA의 배열 길이와 문자열 길이 (0) | 2017.07.30 |
접근제어자 public, protected, private (0) | 2017.07.30 |