1. 싱글톤 패턴??

인스턴스를 오직 한 개만 제공하기 위한 객체 생성을 위한 디자인 패턴이다.

 

2. 멀티쓰레드 환경에서의 동시성 문제

 멀티쓰레드 환경에서는 경합 조건(race condition) 을 고려해야 한다. 특히 싱글톤의 경우에는 경합 조건에 의해 객체가 두 개 생성될 수 있다. 이를 방지하기 위해서는 동기화 메커니즘을 사용해야 하며 모니터(monitor) 기반의 synchronized 키워드를 사용해 임계 영역(critical section) 에 대해 접근을 제어할 수 있다. synchronized 을 사용 시에는 성능을 위해 임계 영역 범위를 최소화하는 것이 중요하다.

 

double checked locking멀티스레드 환경에서 객체 생성을 최적화하는 데 사용되는 소프트웨어 디자인 패턴이다. 싱글톤 패턴과 같은 상황에서 객체가 한 번만 생성되도록 보장하면서 동시에 여러 스레드가 객체를 생성하려는 경우 발생하는 성능 저하를 방지하는 데 효과적이다.

 

 또한, 멀티스레드 환경을 사용하는데는 volatile 을 사용을 고려해야 한다. volatile 은 메모리 가시성을 보장하는 키워드이다. 메모리 가시성은 멀티스레드 환경에서 여러 스레드가 공유 메모리에 대한 액세스와 변경 사항을 서로 얼마나 일관되게 볼 수 있는지를 나타내는 개념을 말한다. 한 스레드가 공유 변수의 값을 변경한 후 다른 스레드가 즉시 변경된 값을 읽지 못하는 경우가 발생할 수 있다.  volatile 을 사용하면 공유 변수가 변경될 경우, 다른 스레드에서 인식해서 값이 변경된다. 관련 코드는 해당 링크를 참고해서 확인해보자.

class Outer {
	
	private static volatile Outer INSTANCE;
	
	private Outer() {}
	
	public static Outer getInstance() {
		// double checked locking
		if (INSTANCE == null) {
			synchronized(Outer.class) {
				if (INSTANCE == null) {
					INSTANCE = new Outer(); 
				}
			}
		}
		return INSTANCE;
	}
}

 

 

3. 효율적인 메모리 관리를 위한 싱글톤 객체 활용법

애플리케이션이 실행되면서 메모리에 초기화하는 작업을 eager initialization 이라 부른다. 하지만  비용이 비싸다면 lazy initialization 을 고려해야 한다. 이는 nested static class 사용해 구현할 수 있다. JVM 의 class loader 는 loading - linking - initialization 작업을 거치는데, initialization 에서 static 필드들을 초기화한다. 하지만 중첩 클래스의 경우, 중첩 클래스를 사용할 때 공유 변수가 초기화 되므로 lazy initialization 을 구현할 수 있다.

class Outer {
	private static class Inner {
		private static final Outer INSTANCE = new Outer();

        public static Outer getInstance() {
	        return INSTANCE;
		}
	}
    
	public static Outer getInstance() {
		return Inner.getInstance();
	}
}



 

 

4. 싱글톤 구현을 깨는 방법

싱글톤 구현을 깨는 방법은 reflectionserialization/deserialization 이 있다.

 

reflection 은 자바 바이트 코드를 통해 접근하여 클래스 기반의 객체는 생성 가능하다. reflection 을 통한 객체 생성을 방지하기 위해서는 enum 을 사용하는 방법도 고려해야 한다.

 

serialization/deserialization 의 경우, 외부 시스템에서 애플리케이션으로 객체가 넘어오면 객체를 새로 생성하기 때문에 싱글톤을 보장할 수 없다. 이를 해결하기 위해서는 readResolve() 을 정의해야 한다. 자바 기반의 애플리케이션이 deserialization 과정에서 해당 메서드를 사용하기 때문에 해당 메서드에 싱글톤 객체를 설정하면 추가 객체 생성을 방지할 수 있다.

public final class Outer implements Serializable {
	private static final Outer INSTANCE = new Outer();

	private Outer() {}

	public static Outer getINSTANCE() {
		return INSTANCE;
	}

	// readResolve 메서드를 정의
	private Object readResolve() {
 		return INSTANCE;
	}
}

 

 

Reference