소개
싱글톤(singleton) 패턴은 인스턴스를 오직 한개만 제공하는 클래스이다.
- 시스템 런타임, 환경 세팅에 대한 정보 등, Connection pool 등 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다. 인스턴스를 오직 한개만 만들어 제공하는 클래스가 필요하다.
구현 방법 1
public class Settings {
// 싱글턴은 static instance를 가지고 있다
private static Settings instance;
// 외부에서 인스턴스 생성을 못하도록 생성자를 private
private Settings() {}
// static method로 인스턴스를 가져온다
// 최초에 instance가 null이면 객체를 생성해서 가지고 있는다
public static Settings getInstance() {
if (instance == null) {
instance = new Settings();
}
return instance;
}
}
위의 방법은 가장 기본적인 싱글톤 패턴 구현이다.
하지만 멀티쓰레드 환경에서 안전하지 않은데 객체를 생성하기 전에 동시에 여러개의 쓰레드가 instance == null 조건을 만족하게 된다면 인스턴스를 여러번 생성하게 된다.
그럼 멀티쓰레드 환경에서 안전한 싱글톤 패턴은 어떻게 구현할까?
구현 방법 2
public static synchronized Settings getInstance() {
if (instance == null) {
instance = new Settings();
}
return instance;
}
synchronized 키워드를 이용해 method 실행시에 Lock을 설정한다.
메소드 전체에 Lock을 걸기 때문에 Instance를 가져올 때마다 Lock이 걸리게 된다.
생성 시 여러개의 쓰레드가 접근하는 걸 막기위해 Lock을 사용했지만, 객체를 가져올 때마다 Lock이 걸리게 때문에 성능에 부작용이 생긴다.
구현 방법 3
private static final Settings INSTANCE = new Settings();
private Settings() {}
public static Settings getInstance() {
return INSTANCE;
}
이 방법은 이른 초기화(eager initialization)를 사용하는 방법이다.
class가 메모리에 로딩되는 시점에 Settings 클래스의 객체를 미리 만들어 놓기 때문에 thread-safe 하다.
다만 인스턴스를 만드는 과정이 길고 메모리를 많이 사용한다면 애플리케이션이 종료될 때 까지 메모리에 남아 있을 것이므로 이 부분이 단점이 될 수 있다. 또한 class 로딩 시 객체가 생성되므로 객체 생성시에 예외(checked exception)를 처리해 주는 부분을 static block 이나 static method로 팩토리 메서드를 만들어 처리해 주어야 한다.
구현 방법 4
public class Settings {
private static volatile Settings instance;
private Settings() {}
public static Settings getInstance() {
if (instance == null) {
synchronized (Settings.class) {
if (instance == null) {
instance = new Settings();
}
}
}
return instance;
}
이 방법은 instance 가 null 인지 두 번 체크한다.
두 번째 if 문 앞에 synchronized 블록을 써줬기 때문에 맨 처음 if 문의 조건을 여러 쓰레드가 통과해도 synchronized 블록 때문에 하나의 쓰레드만 두 번째 if 문을 통과하게 된다.
getInstance() 메소드에 synchronized 키워드를 붙인 것과는 다르게 초기에 instance를 생성하고 나서 부터는 Lock이 걸리지 않는다.
위의 방법은 java의 volatile이란 키워드를 static 변수에 써줘야 하기 때문에 좋지 않은 방법이다. volatile 이란 키워드를 갖고 선언된 변수는 Main Memory에 저장이 되기 때문에 기본적으로 사용하는 CPU cache 보다 read & write 비용이 더 크다.
구현 방법 5
private Settings() {}
// static inner class
private static class SettingHolder {
private static final Settings SETTINGS = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.SETTINGS;
}
getInstance() 메서드를 처음 호출 할 때 SettingsHolders.SETTINGS static 변수가 메모리에 올라가고 계속 재사용되므로 권장하는 방법 중 하나이다.
참고
백기선님의 코딩으로 학습하는 GoF의 디자인 패턴 강의를 정리했습니다.
'자바 & 스프링' 카테고리의 다른 글
java - String, StringBuilder, StringBuffer (0) | 2021.12.08 |
---|---|
JDBC Connection pool은 왜 필요할까? (0) | 2021.12.01 |
PreparedStatement 쿼리를 사용하는 이유 (0) | 2021.11.30 |
JPA allocationSize default 값이 50인 이유 (0) | 2021.11.30 |
java - thread 동기화 (0) | 2021.11.30 |