본문 바로가기
Server System/Software Architecture

싱글톤의 모든 것. Singleton A to Z

by 알파해커 2020. 5. 12.
반응형

싱글톤의 모든 것. 까지는 좀 구라고.

'이 정도 알면 어디가서 싱글톤이 뭔지는 안다고 할 수 있다' 정도가 아닐까 싶다.


1. 전통적인 싱글톤 방식

public class Singleton {
    private static Singleton uniqueInstance; // other instance variable in here
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

문제점: 멀티스레드 환경에서, if 문에 두 스레드(혹은 그 이상)가 동시에 들어가서 인스턴스가 두 개(혹은 그 이상)가 만들어질 수 있음.


2. Synchronized 적용

싱글톤 할 때, synchronized 쓰는게 제일 간단한 방법. synchronizedlocking 방법임.

public class Singleton {
    private static Singleton uniqueInstance;
    // other instance variable in here
    public static synchronized Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance; 
    }
    // other methods.. 
}

문제점: 싱글톤은 만들고자 하는 인스턴스를 최초로 생성하는 순간에만 locking이 되면 되는데, 해당 방법으로는 생성 시도가 있을때마다 locking을 하게 되므로, 성능 이슈가 발생할 수 있음.

 


3. 클래스 로딩 시점에 생성 JVM이 보장해주는 스레드 세이프 방식.

클래스 로드될 때 Singleton 인스턴스가 생성.

public class Singleton {
	private static Singleton uniqueInstance = new Singleton();
    public static Singleton getInstance() {
    	return uniqueInstance;
    }
}

싱글톤 패턴을 구현하는 가장 간단한 방법

문제점: 클라이언트에서 사용하지 않더라도 인스턴스는 항상 생성된다는 것이 단점

https://yaboong.github.io/design-pattern/2018/09/28/thread-safe-singleton-patterns/


4. DCL(Double Checking Locking)

인스턴스가 생성되어 있는지 확인한 다음, 생성되어 있지 않았을 때만 동기화

public class Singleton {
	private ​volatile​ static Singleton uniqueInstance; private Singleton(){}
	public static Singleton getInstance() {
		if (uniqueInstance == null) { 
        	synchronized (Singleton.class) {
				if (uniqueInstance == null) { 
                	uniqueInstance = new Singleton();
				} 
			}
		}
		return uniqueInstance; 
	}
}

GetInstance 안에 두 개의 if 문을 두고, 더블 체킹하는것.
문제점: 이 때, 하나의 if 문 안에 들어갔을 때, synchorized locking하는 방식. 이 방법은 캐시 관련한 문제가 있음. 그래서 반드시 volatile 써야 함. 근데 volatile를 씀으로 해서, 성능 문제가 생김.

 

volatile

- volatile 변수는 CPUCache를 거치지 않고 메인 메모리에 직접 read/write를 수행

- volatile 변수에 대한 접근(read/write)synchronized를 사용하는 것과 동일하게 동작
- primitive
타입과 object 타입(null 허용) 모두 사용 가능.


5. Enum

1. 구현이 쉬움.

2. Enum은 스레드 세이프하게 구현되었음

(그렇다고 Enum 내부에 사용자가 구현하는 메소드들도 스레드 세이프가 보장되는 것은 아님)

3.직렬화/역직렬화 에 대한 처리가 필요없다.

 

문제점: Lazy Loading이 아님


6. LazyHolder 기법

원할때 만들고, 스레드 세이프하게하기 위해

public enum EnumSingleton { 
	INSTANCE;
	public void someMethod(String param) { }
}

public class Singleton { 
	private Singleton() {}
	public static Singleton getInstance() { 
    	return LazyHolder.INSTANCE;
	}

	private static class LazyHolder {
		private static final Singleton INSTANCE = new Singleton(); 
    }
}

Singleton 클래스 안에 Holder 클래스를 두고, JVMClass loader 규칙에 의해 Lazy Loading 하는 것을 보장.

 

Java는 동적으로 클래스를 로딩하는 2가지 방식이 있음.

  • 로드타임 동적 로딩(Load-time Dynamic Loading)

  • 런타임 동적 로딩(Run-time Dynamic Loading)

     

로드타임 동적 로딩은 클래스 내부에 다른 클래스 정보가 있다면 모두 로드하는 방식이고,

런타임 동적 로딩은 실제 클래스 정보를 필요로 할 때 로드하는 방식.

 

여기서 LazyHolder는 ​런타임 로드이​기 때문에, 실제로 필요로 하기 전 까지 JVM에 올라오지 않음.

 

여러 쓰레드에서 LazyHolder를 호출해도 ​JVM이 알아서 하나만 올려주기 때문에 synchronized, volatile과 같이 동기화를 위한 키워드를 쓰지 않아도 되고 Java 버전을 타지도 않는 장점​이 있음.

 

Q. “2. 클래스 로딩 시점에 생성방식과 다른점은?
A.
클래스가 로드될때 싱글톤 객체가 생성되는것과 실제 getInstance를 호출할때 싱글톤 객체가 생성되는것의 차이다.

2번 방식의 경우, getInstance 외에 또다른 static 메서드를 만들고, 해당 메서드를 실행하게 되면, getInstance를 실행하지 않아도 싱글톤 객체가 만들어진다. 클래스가 그 순간에 로딩되기 때문에. 다시 말해, 원하지 않을때 객체가 생성된다는 것.

그러나, 내부 클래스로 만들어놓으면(LazyHolder기법) 위와같은 상황에서도(클래스가 로드되는 상황에서도) 싱글톤 객체는 생성하지 않는다. , getInstance를 호출해야만 싱글톤 객체가 생성된다.

 

반응형

댓글