[JAVA/디자인패턴] Singleton Pattern, 싱글턴 패턴

2023. 10. 5. 12:40
반응형

Singleton pattern

 

https://en.wikipedia.org/wiki/Singleton_pattern

 

클래스가 하나의 인스턴스만을 만들 수 있도록 하고, 어디서나 생성된 인스턴스에 접근할 수 있도록 하는 디자인 패턴을 말한다.

시스템에서 다른 상태의 인스턴스가 여러 개 필요 없는 경우 해당 디자인 패턴을 사용한다. 

여러 객체가 생성되면 관리가 어려우며, 이를 위해 객체 생성자를 중앙 관리하게 하고, 객체가 1개라서 일관된 상태의 결과를 만들게 된다.

 

 

 

 

 


고전적인 싱글턴 패턴

 

public class Singleton {

	private static Singleton instance = null;
	
	private Singleton() {
		System.out.println("Singleton constructor");
	}
	
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
	
	public void print() {
		System.out.println("Singleton instance hashCode = " + instance.hashCode());
	}
}
public class SingletonMain {
	public static void main(String[] args) {
		Singleton instance = Singleton.getInstance();
		instance.print();
	}
}

 

다음은 전통적으로 사용하던 싱글턴 패턴을 적용한 클래스이다. 

해당 클래스의 생성자를 눈여겨보면 private으로 선언되어 있다. 따라서 클래스 외부에서 new를 통해 새로운 인스턴스를 생성할 수 없다.

대신 해당 클래스를 사용하려면 getInstance()를 호출하여 사용한다. 

getInstance()는 해당 클래스의 멤버변수로 선언된 나 자신의 Class(Singleton)의 인스턴스가 없을 경우 인스턴스를 생성한 후, 해당 인스턴스를 반환하며, 이미 인스턴스가 존재하는 경우, 해당 인스턴스를 반환하여, 다른 클래스에서 단일 인스턴스를 사용할 수 있도록 한다.

 

 

Thread-Unsafe

 

위의 싱글턴 패턴을 적용한 코드는 이론적으로는 괜찮아 보이지만, Thread-Unsafe한 코드라고 볼 수 있다.

Thread-unsafe 여러 스레드에서 동시에 접근할 예기치 않은 동작이 발생할 있는 코드를 의미한다.

코드에서의 Thread-unsafe 문제는 다중 스레드 환경에서 getInstance() 메서드를 통해 인스턴스를 가져오는 동안 발생할 있다.

 

Singleton 클래스의 인스턴스가 생성되지 않은 상태에서, 만약 여러개의 스레드들이 동시에 getInstace()를 호출하게 될 때에

먼저 접근한 스레드의 인스턴스 생성 작업이 완료되지 않았다면, 그 다음 스레드 또한 새로운 인스턴스를 생성하게 되는 문제가 발생할 수 있다. 즉 하나의 클래스는 하나의 인스턴스만 생성하여 사용하겠다는 Singleton Pattern의 의미가 퇴색되게 된다.

 

 

 


Synchronized

 

public class Singleton {

	private static Singleton instance = null;
	
	private Singleton() {
		System.out.println("Singleton constructor");
	}
	
	public static synchronized Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
	
	public void print() {
		System.out.println("Singleton instance hashCode = " + instance.hashCode());
	}
}

getInstance()에 synchronized 키워드를 추가하면 해당 메서드 전체에 대한 Lock이 걸리게 된다.

이는 여러 스레드가 동시에 이 메서드에 접근할 때, 하나의 스레드만이 해당 메서드를 실행할 수 있도록 할 수 있다.

이는 인스턴스가 항상 하나만 생성되도록 보장한다.

그러나 synchronized 키워드는 성능 저하를 초래할 수 있다. 왜냐하면, 다른 스레드가 해당 메서드에 접근하려 할 때, 기다려야 하기 때문이다. 따라서 성능상의 이슈가 발생할 수 있다는 문제점이 있다.

 

따라서 싱글턴 클래스의 경우 인스턴스를 프로그램이 시작될 때 생성하는 방법을 주로 사용하게 되고, Spring을 비롯한 다수의 프레임워크가 해당 방식으로 Bean을 등록하는 방식을 채택하고 있다.

 

 

 

 


DCL(Double-checking Locking)과 Volatile

 

public class Singleton {

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

	
	public void print() {
		System.out.println("Singleton instance hashCode = " + instance.hashCode());
	}
}

 

해당 코드에서는 getInstance()에서 DCL을 사용하여 인스턴스가 생성되어 있는 지 확인 후, 생성되어 있지 않았을 때(null일 때)에만 동기화를 시킬 수 있도록 하였다. 이렇게 하면, 첫 인스턴스가 생성된 이후에는 동기화가 이루어지지 않으므로 성능 저하 문제를 완화시킬 수 있다.

 

volatile 키워드는 해당 변수에 대한 쓰기(write)와 읽기(read)가 항상 메인 메모리에서 이루어지도록 보장한다. 이를 통해 변수의 변경이 즉시 다른 스레드에서 반영되도록 할 수 있다.

 

반응형

BELATED ARTICLES

more