Design pattern
[Design Pattern] Singleton Pattern
s00ng
2022. 7. 20. 18:12
1. 싱글턴 패턴 정의
인스턴스가 오직 하나만 생성되는 것을 보장하고, 어디에서든 이 인스턴스에 접근할 수 있도록 하는 디자인 패턴
- 하나의 인스턴스만을 생성하는 책임 있음
- getInstance 메서드를 통해 모든 클라이언트에게 동일한 인스턴스 반환하는 작업 수행
2. 싱글턴 패턴 구현
- 생성자의 접근 지정자 private으로 설정
- getter를 통해 인스턴스를 제공
다중 스레드 애플리케이션에서 (싱글톤 구현시) 발생하는 문제
경합 조건(race condition) : 메모리와 같은 동일한 자원을 2개 이상의 스레드가 이용하려고 경합하려는 현상
※ 해결 방법 ※
1. 정적 변수에 인스턴스를 만들어 바로 초기화 (Eager Initialization)
2. 인스턴스를 만드는 메서드에 동기화 (Thread-Safe Initialization)
1) 정적 변수에 인스턴스를 만들어 바로 초기화하는 방법
public class Printer {
// static 변수에 외부에 제공할 자기 자신의 인스턴스를 만들어 초기화
private static Printer printer = new Printer();
private Printer() { }
// 자기 자신의 인스턴스를 외부에 제공
public static Printer getPrinter(){
return printer;
}
public void print(String str) {
System.out.println(str);
}
}
- static 변수
→ 객체가 생성되기 전 클래스가 메모리에 로딩될 때 만들어져 초기화가 한 번만 실행
→ 프로그램 시작~종료까지 없어지지 않고 메모리에 계속 상주하며 클래스에서 생성된 모든 객체에서 참조 가능
2) 인스턴스를 만드는 메서드에 동기화하는 방법
public class Printer {
// 외부에 제공할 자기 자신의 인스턴스
private static Printer printer = null;
private int counter = 0;
private Printer() { }
// 인스턴스를 만드는 메서드 동기화 (임계 구역)
public synchronized static Printer getPrinter(){
if (printer == null) {
printer = new Printer(); // Printer 인스턴스 생성
}
return printer;
}
public void print(String str) {
// 오직 하나의 스레드만 접근을 허용함 (임계 구역)
// 성능을 위해 필요한 부분만을 임계 구역으로 설정한다.
synchronized(this) {
counter++;
System.out.println(str + counter);
}
}
}
- 인스턴스를 만드는 메서드를 임계 구역으로 변경
→ 다중 스레드 환경에서 동시에 여러 스레드가 getPrinter 메서드를 소유하는 객체에 접근하는 것을 방지
- 공유 변수에 접근하는 부분을 임계 구역으로 변경
→ 여러 개의 스레드가 하나뿐인 counter 변수 값에 동시에 접근해 갱신하는 것을 방지
3. 정적 클래스 (static class)
정적 메서드로만 이루어진 정적 클래스를 사용하면 싱글턴과 동일한 효과를 얻을 수 있음
public class Printer {
private static int counter = 0;
// 메서드 동기화 (임계 구역)
public synchronized static void print(String str) {
counter++;
System.out.println(str + counter);
}
}
public class UserThread extends Thread{
// 스레드 생성
public UserThread(String name) { super(name); }
// 현재 스레드 이름 출력
public void run() {
Printer.print(Thread.currentThread().getName());
}
}
public class Client {
private static final int THREAD_NUM = 5;
public static void main(String[] args) {
UserThread[] user = new UserThread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++) {
// UserThread 인스턴스 생성
user[i] = new UserThread((i+1));
user[i].start();
}
}
}
싱글톤 패턴과의 차이점
- 정적 클래스를 이용하면 객체를 전혀 생성하지 않고 메서드를 사용한다.
- 정적 메서드를 사용하므로 일반적으로 실행할 때 바인딩되는(컴파일 타임에 바인딩되는) 인스턴스 메서드를 사용하는 것보다 성능 면에서 우수
정적 클래스를 사용할 수 없는 경우
- 인터페이스를 구현해야 하는 경우, 정적 메서드는 인터페이스에서 사용할 수 없음