싱글턴 패턴 (Singleton Pattern)
- 생성 패턴 (Creational Pattern) 중 하나
=> 인스턴스를 만드는 절차를 추상화
=> 객체를 생성, 합성하는 방법이나 객체의 표현 방법을 시스템과 분리해줍니다.
=> 무엇이 생성되고, 누가 이것을 생성하는지, 어떻게 생성되는지, 언제 생성되는지 결정하는 데 있어서 유연성을 높일 수 있습니다.
// 장점 //
- 한 번의 new 생성으로 인스턴스를 사용하기 때문에 메모리 낭비 방지
- 싱글톤 패턴으로 만들어진 클래스의 인스턴스는 전역 인스턴스(static instance)이기 때문에, 다른 클래스의 인스턴스들이 데이터를 공유하기 쉬움
- Android App의 경우, 각 액티비티나 클래스 별로 주요 클래스들을 일일이 전달하기가 번거롭기 때문에 싱글톤 클래스를 만들어 어디서나 접근하도록 설계하기에 편함
- 첫 번째 생성한 뒤부터는, 이후에 사용 시에 객체 로딩 시간이 현저하게 줄음
// 단점 //
- 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유시킬 경우, 다른 클래스의 인스턴스들 간의 결합도가 높아져서 개방-폐쇄 원칙(OCP)를 위배하게 됨
- 즉, 객체 지향 설계 원칙을 위배하며 수정이 어려워지고 테스트하기 어려워짐
아래 예시에 사용될 Printer 객체
import java.util.LinkedList;
import java.util.Queue;
public class Printer {
private static Printer printer;
private static Queue<String> printQueue;
private Printer() {
this.printQueue = new LinkedList<>();
}
public static Printer getInstance() {
if (printer == null) {
printer = new Printer();
}
return printer;
}
public static void addPrint(String s) {
printQueue.add(s);
}
public static String printStr() {
return printQueue.poll();
}
}
1. 싱글턴 패턴 예시
public class PrintUsers {
public static void main(String[] args) {
// user1
Printer printer1 = Printer.getInstance();
printer1.addPrint("a");
printer1.addPrint("b");
printer1.addPrint("c");
printer1.addPrint("d");
// user2
System.out.println("---User2---");
Printer printer2 = Printer.getInstance();
System.out.println(printer2.printStr());
System.out.println(printer2.printStr());
printer2.addPrint("e");
printer2.addPrint("f");
System.out.println(printer2.printStr());
System.out.println(printer2.printStr());
System.out.println(printer2.printStr());
}
}
이때 user1과 user2가 같은 인스턴스를 사용하므로, user1이 Printer에 넣은 a, b, c, d가 user2가 생성한 인스턴스에서 출력되는 것을 볼 수 있습니다.
문제점
그러나, 이러한 Printer 객체는 다중 스레드 환경에서 문제점이 발생할 수 있습니다.
=> 경합 조건 (Race Condition)을 발생시키는 경우
1. Printer 인스턴스가 아직 null인 상황에서,
Thread1이 Printer에 접근하려고 인스턴스가 null인지 확인한다.
2. 이때, Thread1이 인스턴스가 null인지 확인하고 인스턴스를 새로 생성하기 전에
Thread2가 Printer 인스턴스에 접근하여 null인지 확인한다.
3. 이 경우, Thread1과 Thread2가 간발의 차이로 둘 다 인스턴스가 null임을 확인하고
새로운 인스턴스를 생성하게 되기 때문에, 인스턴스가 2개 만들어지게 되는 문제가 발생한다.
해결책
1. 정적 변수에 인스턴스를 만들어 바로 초기화 하는 방법 (Eager Initialization)
import java.util.LinkedList;
import java.util.Queue;
public class Printer {
/*
객체가 생성되기 전, 클래스가 메모리에 로딩될 때 만들어질 때 딱 한 번 초기화가 실행된다.
프로그램 시작~종료까지 없어지지 않고 항상 메모리에 상주하여 클래스에서 생성된 모든 객체에서 참조할 수 있다.
*/
private static Printer printer = new Printer();
private static Queue<String> printQueue;
private Printer() {
this.printQueue = new LinkedList<>();
}
public static Printer getInstance() {
return printer;
}
public static void addPrint(String s) {
printQueue.add(s);
}
public static String printStr() {
return printQueue.poll();
}
}
2. 인스턴스를 만드는 메서드에 동기화하는 방법 (Thread-Safe Initialization)
import java.util.LinkedList;
import java.util.Queue;
public class Printer {
private static Printer printer; // null
private int count = 0;
private static Queue<String> printQueue;
private Printer() {
this.printQueue = new LinkedList<>();
}
public synchronized static Printer getInstance() {
// 임계구역 //
if (printer == null) {
printer = new Printer();
}
return printer;
}
public static void addPrint(String s) {
printQueue.add(s);
}
public String printStr() {
// 임계구역 //
// 오직 하나의 Thread만 접근 허용 //
// 성능을 위해 필요한 부분만 임계 구역으로 설정 //
synchronized (this) {
return printQueue.poll();
}
}
}
참고 :
'스터디 > Design Pattern' 카테고리의 다른 글
디자인 패턴 - 전략 패턴 (Strategy Pattern) (0) | 2020.05.31 |
---|---|
디자인 패턴 - 옵저버 패턴 (Observer Pattern) (0) | 2020.05.30 |
디자인 패턴 - 추상 팩토리 패턴 ( Abstract Factory Pattern ) (0) | 2020.05.30 |
디자인 패턴 - 팩토리 메소드 패턴 (Factory Method Pattern) (0) | 2020.05.30 |
디자인 패턴 - 프로토타입 패턴 (Prototype Pattern) (0) | 2020.05.29 |
댓글