[JAVA/디자인패턴] Singleton Pattern, 싱글턴 패턴
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)가 항상 메인 메모리에서 이루어지도록 보장한다. 이를 통해 변수의 변경이 즉시 다른 스레드에서 반영되도록 할 수 있다.
'CS > Design pattern' 카테고리의 다른 글
[JAVA/디자인패턴] Factory : 팩토리 메서드 패턴, 추상 팩토리 패턴 (0) | 2023.10.20 |
---|---|
[JAVA/디자인패턴] Decorator Pattern, 데코레이터 패턴 (1) | 2023.10.15 |
[JAVA/디자인패턴] Observer Pattern, 관찰자 패턴 (1) | 2023.10.06 |
[JAVA/디자인패턴] Strategy Pattern, 전략 패턴 (1) | 2023.10.05 |
[JAVA/디자인패턴] Iterator Pattern, 반복자 패턴 (1) | 2023.10.05 |