반응형
의도
- 오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공한다.
활용성
- 클래스의 인스턴스가 오직 하나여야 함을 보장하고, 잘 정의된 접근점(acces point)으로 모든 사용자가 접근할 수 있도록 해야 할 때
- 유일한 인스턴스가 서브클래싱으로 확장되어야 하며, 사용자는 코드의 수정없이 확장된 서브클래스의 인스턴스를 사용할 수 있어야 할 때
결과
- 유일하게 존재하는 인스턴스로의 접근을 통제한다.
- Singleton 클래스 자체가 인스턴스를 캡슐화하기 때문에, 이 클래스에서 사용자가 언제, 어떻게 이 인스턴스에 접근할 수 있는지 제어할 수 있다.
- 이름 공간(name space)을 좁힌다.
- 단일체 패턴은 전역 변수보다 더 좋다.
- 전역 변수를 사용해서 이름 공간을 망치는 일을 없애준다. 즉, 디버깅의 어려움 등의 문제를 없앤다.
- 연산 및 표현의 정제를 허용한다.
- Singleton 클래스는 상속될 수 있기 때문에, 이 상속된 서브클래스를 통해서 새로운 인스턴스를 만들 수 있다.
- 또한 이 패턴을 사용하면, 런타임에 필요한 클래스의 인스턴스를 써서 응용프로그램을 구성할 수도 있다.
- 인스턴스의 개수를 변경하기가 자유롭다.
- Singleton 클래스의 인스턴스가 하나 이상 존재할 수 있도록 변경해야 할 경우에 이 작업도 어렵지 않다.
- Singleton 클래스의 인스턴스에 접근할 수 있는 허용 범위를 결정하는 연산만 변경하면 된다.
- 기존에는 하나의 인스턴스로만 접근을 허용했다면, 이제는 여러 개의 인스턴스를 생성해서 그 각각의 인스턴스로 접근할 수 있도록 연산의 구현을 바꾸면 되기 때문이다.
협력 방법
- 사용자는 Singleton 클래스에 정의된 Instance() 연산을 통해서 유일하게 생성되는 단일체 인스턴스에 접근할 수 있다.
구조
실제 구현 구조
구현 - Lazy initialization(지연 초기화)
- 인스턴스가 필요한 시점에 인스턴스를 생성한다.
- 쓰레드에 안전하지 않다.
- 동시에 인스턴스 생성이 호출되어 인스턴스가 여러 개 생성될 수 있다.
소스코드
//Singleton
public class lazyInitPrinter {
private static lazyInitPrinter printer;
private lazyInitPrinter() {}
public static lazyInitPrinter getInstance() {
if(printer == null) {
printer = new lazyInitPrinter();
}
return printer;
}
public void print(String string) {
System.out.println("[lazyInitPrinter] " + string);
}
}
public class Main {
public static void main(String[] args) {
PrinterSelecter printerSelecter = new PrinterSelecter();
Thread thread1 = new Thread(() -> {
for(int i = 0; i < 5 ; i++) {
System.out.println(printerSelecter.getLazyInitPrinter() + " : " + Thread.currentThread().getName());
}
});
thread1.start();
Thread thread2 = new Thread(() -> {
for(int i = 0; i < 5 ; i++) {
System.out.println(printerSelecter.getLazyInitPrinter() + " : " + Thread.currentThread().getName());
}
});
thread2.start();
System.out.println(printerSelecter.getLazyInitPrinter() + " : " + Thread.currentThread().getName());
}
}
- 멀티 스레드 환경에서 싱글톤 객체의 인스턴스 생성이 동시에 여러 번 호출 되어 하나의 인스턴스만 가져야하는 싱글톤 객체가 어느 시점에 여러 개의 인스턴스를 가지고 있다.
구현 - Lazy initialization with synchronized (동기화 블럭 사용한 지연 초기화)
- 동기화 블럭을 사용하여 지연 초기화 방식의 싱글톤 패턴을 멀티쓰레드에 안전하도록 개선
- 그러나 synchronized 키워드를 사용하면 성능이 감소한다.
소스코드
//Singleton
public class threadSafeLazyInitPrinter {
private static threadSafeLazyInitPrinter printer;
private threadSafeLazyInitPrinter() {}
public static synchronized threadSafeLazyInitPrinter getInstance() {
if(printer == null) {
printer = new threadSafeLazyInitPrinter();
}
return printer;
}
public void print(String string) {
System.out.println("[threadSafeLazyInitPrinter]" + string);
}
}
public class Main {
public static void main(String[] args) {
PrinterSelecter printerSelecter = new PrinterSelecter();
Thread thread1 = new Thread(() -> {
for(int i = 0; i < 5 ; i++) {
System.out.println(printerSelecter.getThreadSafeLazyInitPrinter() + " : " + Thread.currentThread().getName());
}
});
thread1.start();
Thread thread2 = new Thread(() -> {
for(int i = 0; i < 5 ; i++) {
System.out.println(printerSelecter.getThreadSafeLazyInitPrinter() + " : " + Thread.currentThread().getName());
}
});
thread2.start();
System.out.println(printerSelecter.getThreadSafeLazyInitPrinter() + " : " + Thread.currentThread().getName());
}
}
구현 - Lazy Initialization. LazyHolder
- 쓰레드에 안전하며 성능도 좋다.
- 싱글톤 패턴을 구현할 때 해당 방식을 많이 사용한다.
소스코드
//Singleton
public class LazyHolderPrinter {
private LazyHolderPrinter() {}
private static class InnerInstanceClazz {
// 클래스 로딩 시점에서 생성
private static final LazyHolderPrinter uniqueInstance = new LazyHolderPrinter();
}
public static LazyHolderPrinter getInstance() {
return InnerInstanceClazz.uniqueInstance;
}
}
public class Main {
public static void main(String[] args) {
PrinterSelecter printerSelecter = new PrinterSelecter();
Thread thread1 = new Thread(() -> {
for(int i = 0; i < 5 ; i++) {
System.out.println(printerSelecter.getLazyHolderPrinter() + " : " + Thread.currentThread().getName());
}
});
thread1.start();
Thread thread2 = new Thread(() -> {
for(int i = 0; i < 5 ; i++) {
System.out.println(printerSelecter.getLazyHolderPrinter() + " : " + Thread.currentThread().getName());
}
});
thread2.start();
System.out.println(printerSelecter.getLazyHolderPrinter() + " : " + Thread.currentThread().getName());
}
}
- LazyHolderPrinter 클래스에는 InnerInstanceClazz 클래스의 변수가 없기 때문에, static 멤버 클래스더라도 클래스 로더가 초기화 과정을 진행할 때 InnerInstanceClazz 메서드를 초기화 하지 않고, getInstance() 메서드를 호출할 때 초기화 된다.
- 동적 바인딩(Dynamic Binding)의 특징을 이용하여 쓰레드에 안전하면서 성능이 뛰어나다.
- InnerInstanceClazz 내부 인스턴스는 static 이기 때문에 클래스 로딩 시점에 한 번만 호출된다는 점을 이용한 것이며, final 키워드를 사용하여 다시 값이 할당되지 않도록 한다.
관련 패턴
- 많은 패턴이 단일체 패턴으로 구현될 수 있다.
- 추상 팩토리, 빌더, 원형 패턴
[참고자료]
리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)
http://www.cs.unc.edu/~stotts/GOF/hires/pat3dfso.htm
https://blog.seotory.com/post/java-singleton-pattern
https://medium.com/webeveloper/싱글턴-패턴-singleton-pattern-db75ed29c36
반응형