헤드 퍼스트 디자인 패턴 | 에릭 프리먼 | 한빛미디어- 교보ebook

14가지 GoF 필살 패턴!, 경력과 세대를 넘어 오랫동안 객체지향 개발자의 성장을 도와준 디자인 패턴 교과서의 화려한 귀환! 》 2005년부터 디자인 패턴 도서 분야 부동의 1위 》 디자인 패턴의 고

ebook-product.kyobobook.co.kr

싱글턴 패턴은 특정 클래스에 객체 인스턴스가 하나만 만들어지도록 해 주는 패턴

 

어플리케이션에서 하나만 있어도 잘 돌아가는 객체들이 있다. 스레드 풀, 캐시, 설정, 로그 등 객체들

이런 객체들은 오히려 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을 쓰면 된다.

source.zip
0.00MB

+ Recent posts