싱글턴 패턴은 특정 클래스에 객체 인스턴스가 하나만 만들어지도록 해 주는 패턴
어플리케이션에서 하나만 있어도 잘 돌아가는 객체들이 있다. 스레드 풀, 캐시, 설정, 로그 등 객체들
이런 객체들은 오히려 2개 이상이면 오동작할 수 있다.
고전적인 싱글턴 패턴 구현법
이 방식을 절대로 쓰면 안된다.
public class ChocolateBoilerTest {
//고전적인 싱글턴, 문제를 멀티스레딩 시 발생
static class ChocolateBoiler{
private boolean empty;
private boolean boiled;
//static
private static ChocolateBoiler boiler;
//생성자 private 이 클래스에서만 접근가능
private ChocolateBoiler() {
empty = true;
boiled = false;
}
//static
public static ChocolateBoiler getInstance() {
//이렇게 객체를 필요할 때 생성하는 방법을 지연로딩이라 한다.
if(boiler == null) {
boiler = new ChocolateBoiler();
}
return boiler;
}
public void fill() {
if(isEmpty()) {
empty = false;
boiled = false;
}
}
public void drain() {
if(!isEmpty()&& isBoiled()) {
empty = true;
}
}
public void boil() {
if(!isEmpty()&& !isBoiled()) {
boiled = true;
}
}
public boolean isEmpty() {
return empty;
}
public boolean isBoiled() {
return boiled;
}
}
}
싱글턴 패턴의 정의
싱글턴 패턴은 클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공한다.
인스턴스는 그 클래스에서 관리하면 된다.
인스턴스는 어디서는 접근할 수 있도록 전역 접근 지점을 제공한다.
지연 로딩 구현 방식은 비용이 큰 객체에 적용하면 유용하다.
멀티쓰레드 문제
객체가 여러 개 생길 수있다.
멀티쓰레딩 문제 해결하기
synchronized 키워드
//가장 간단한 방법, 성능 저하 약 100배, 이유는 매번 호출 마다 동기화하기 때문
public static synchronized ChocolateBoiler getInstance() {
if(boiler == null) {
boiler = new ChocolateBoiler();
}
return boiler;
}
synchronized 키워드를 추가하면 한 스레드가 메서드 사용을 끝내기 전까지 다른 쓰레드는 기다려야 한다.
이 방법에 문제는 객체 생성 처음에만 유효할 뿐 이후 호출에는 전혀 쓸모없는 오버헤드를 유발한다.
더 효율적으로 멀티스레딩 문제 해결하기
현재 이 방법이 부하가 크지 않다면, 그대로 사용해도 무방하다.
처음부터 초기화
static class ChocolateBoiler{
private boolean empty;
private boolean boiled;
//시작시 바로JVM이 정적 초기화
//단점으로 지연로딩을 원한다면 불가능하다.
private static ChocolateBoiler boiler = new ChocolateBoiler();
DCL(Double-Checked Locking) 사용
처음에만 동기화하고 이후 동기화 안하게 된다.
static class ChocolateBoiler{
private boolean empty;
private boolean boiled;
//volatile, 객체 생성 시 CPU 캐시되는 것을 방지하기 위함
private static volatile ChocolateBoiler boiler;
private ChocolateBoiler() {
empty = true;
boiled = false;
}
//DCL 이중확인 락 방법, 맨 처음에만 동기화하고 이 후 동기화 안함.
public static ChocolateBoiler getInstance() {
if(boiler == null) {
synchronized (ChocolateBoiler.class) {
if(boiler == null) {
boiler = new ChocolateBoiler();
}
}
}
return boiler;
}
이 방법은 JDK1.4 이전 버전에선 정상동작을 보장못한다. volatile 키워드를 써도 동기화가 제대로 안 된다.
궁극의 해결법 enum
enum을 사용하면, 동기화 문제, 클래스 로딩 문제, 리플렉션, (역)직렬화 문제 등 모든 문제가 해결된다.
public class ChocolateBoilerTest5 {
static enum ChocolateBoiler{
BOILER;
private boolean empty;
private boolean boiled;
private ChocolateBoiler() {
empty = true;
boiled = false;
}
public void fill() {
if(isEmpty()) {
empty = false;
boiled = false;
}
}
public void drain() {
if(!isEmpty()&& isBoiled()) {
empty = true;
}
}
public void boil() {
if(!isEmpty()&& !isBoiled()) {
boiled = true;
}
}
public boolean isEmpty() {
return empty;
}
public boolean isBoiled() {
return boiled;
}
}
}
핵심 정리
- 싱글턴 패턴을 적용하면, 그 클래스 객체는 한 개만 존재한다.
- 객체는 어디서든 접근하도록 해야한다.
- 생성자는 private
- 클래스 로더가 여러 개 있으면 여러 개 인스턴스가 생길 수 있어 주의해야 한다
- enum 지원하기 시작한 jdk 1.5 이후 부턴 싱글턴 생성 시 enum을 쓰면 된다.
'IT책, 강의 > 헤드 퍼스트 디자인 패턴' 카테고리의 다른 글
CHAPTER 07 적응시키기 어댑터 패턴과 퍼사드 패턴 (0) | 2023.05.26 |
---|---|
CHAPTER 06 호출 캡슐화하기 커맨드 패턴 (0) | 2023.05.24 |
CHAPTER 04 객체지향 빵 굽기 팩토리 패턴 (0) | 2023.05.19 |
CHAPTER 03 객체 꾸미기 데코레이터 패턴 (1) | 2023.05.17 |
CHAPTER 02 객체들에게 연락 돌리기 옵저버 패턴 (0) | 2023.05.15 |