개발/Java

디자인 패턴 - Singleton Pattern

haloper 2016. 3. 22. 14:15

싱글턴 패턴은 클래스의 인스턴스 갯수를

단 하나만 생성 가능 하도록 제한하는 방법입니다.


중요한 상태값 정보를 가지고 있는 클래스일 경우

인스턴스가 여러 개 생성되어

다양한 상태값을 갖게 되면

프로그램에 큰 문제를 발생 시킬 수 있어

싱글턴 클래스 사용이 필요합니다.

또는 불필요하게 인스턴스의 수가 많이 생성되어

메모리를 쓸데 없이 잡아먹고 있는 것이 싫은 때에도

싱글턴 클래스를 사용할 수 있습니다.


싱글턴 패턴의 구현 방법은 다음과 같습니다.

기본적으로 default 생성자를 private으로 선언하여

new 키워드를 이용한 객체 생성을 막습니다.

그리고 public static method인 getInstance를 만들고

해당 method만 이용하여 인스턴스를 전달 받도록 합니다.

getInstance method 안에서 객체의 인스턴스 수를 제한하면 됩니다.


가장 기본적인 싱글턴 패턴의 구현 방법입니다.

BasicSingleton.java

public class BasicSingleton {
	
	private static BasicSingleton instance = new BasicSingleton();
	
	private BasicSingleton() {
		System.out.println("BasicSingletone 생성");
	}
	
	public static BasicSingleton getInstance() {
		return instance;
	}

}


하지만, 이 방법은 static field로 객체의 인스턴스를 가지고 있기 때문에

프로그램 시작 시에 해당 객체의 인스턴스가 메모리 상에 로드되어,

이 클래스를 한번도 사용하지 않는 상황에서도

리소스를 항상 차지하고 있게 됩니다.

이런 문제를 해결 하기 위해서는

인스턴스 필요시에 생성하여 전달해주는 방식으로 수정이 필요합니다.


LazySingleton

public class LazySingleton {
	
	private static LazySingleton instance;
	
	private LazySingleton() {
		System.out.println("LazySingleton 생성");
	}
	
	public static LazySingleton getInstance() {
		if(instance == null) {
			instance = new LazySingleton();
		}
		return instance;
	}

}


getInstance의 최초 호출 시에만 인스턴스를 생성하고,

이후에는 이미 생성한 인스턴스를 돌려주기만 합니다.

인스턴스를 필요할 때에 생성하도록 하는 문제는 해결되었지만,

위 방식은 멀티쓰레드 환경에서 아주 위험한 문제를 가지고 있습니다.

if(instance == null)

이 비교문을 두 개의 쓰레드가 동시에 진입 했을 경우

인스턴스 생성문이 두 번 호출되는 문제가 있습니다.

이는 심각한 문제이면서도 상시 발생하는 문제가 아니라

디버깅이 굉장히 어려울 수 있습니다.


멀티쓰레드 관련 이슈를 해결하기 위해서 

getInstance method에 synchronized 키워드를 사용할 수 있습니다.


*  LazySingletonSync

public class LazySingletonSync {
	
	private static LazySingletonSync instance;
	
	private LazySingletonSync() {
		System.out.println("LazySingletonSync 생성");
	}
	
	public static synchronized LazySingletonSync getInstance() {
		if(instance == null) {
			instance = new LazySingletonSync();
		}
		return instance;
	}

}


synchronized 키워드는

아무래도 성능을 저하시키는 문제가 있습니다.

게다가 싱글턴 클래스에서는

인스턴스 생성 시에만 동기화 처리하면 되기 때문에

다음과 같이 개선하는 것이 성능상 더 좋습니다.


*  LazySingletonSync2

public class LazySingletonSync2 {
	
	private volatile static LazySingletonSync2 instance;
	
	private LazySingletonSync2() {
		System.out.println("LazySingletonSync2 생성");
	}
	
	public static LazySingletonSync2 getInstance() {
		if(instance == null) {
			synchronized(LazySingletonSync2.class) {
				if(instance == null) {
					instance = new LazySingletonSync2();
				}
			}
		}
		return instance;
	}

}


volatile 키워드는 해당 변수의 원자성을 지켜주는 키워드 입니다.

관련된 설명은 아래 링크 참조 부탁드립니다.

http://blog.javarouka.me/2012/04/volatile-keyword-in-java.html