싱글톤 패턴 (Singleton Pattern)
싱글톤 패턴은 디자인 패턴들 중 하나로 정의로 보았을 때는 단순해 보이지만 구현을 했을 때는 복잡한 것이 많은 패턴이다.
싱글톤 패턴의 원리
- 싱글톤 패턴은 클래스의 인스턴스화를 제한하고 Java Virtual Machine 에 클래스의 인스턴스가 하나만 존재하도록 보장한다.
- 싱글톤 클래스는 클래스의 인스턴스를 가져오기 위한 전역 액세스 클래스를 제공해야 한다.
- 싱글톤 패턴은 데이터베이스 연결 관리, 설정 정보 관리, 로깅, 드라이버 객체, 캐싱, 스레드풀 에 사용된다.
- 싱글톤 디자인 패턴 Abstract Factory, Builder, Prototype, Facade 등의 타입에도 사용된다.
- 싱글톤 패턴은 Java 의 주요 핵심 클래스에도 사용이 된다. (예를 들면, java.Runtime, java.awt.Desktop..)
싱글톤 패턴은 하나의 클래스에 하나의 인스턴스만 가지는 패턴이다. 무슨 말인가 하면, 메모리 절약을 위해 인스턴스가 필요할 대 똑같은 인스턴스를 굳이 새로 만들지 않고 기존의 인스턴스를 가져와서 활용하는 기법인 것이다.
전역 접근 클래스를 사용함으로써, 메서드 마다 지역 변수로 선언해서 사용하는 것 과 같은 반복적이고 비효율적인 방법대신 전역 변수를 사용하는 것 처럼 특정 클래스에 전역 접근이 가능하도록 하면서 인스턴스가 하나만 존재하도록 하면 된다.
이러한 싱글톤 패턴은 공유 리소스에 대한 일관된 접근 지점 제공, 일관된 상태 유지와 같은 시스템에서 유용하다.
싱글톤 패턴 구현
싱글톤 패턴 구현에는 다양한 방식이 있다. (책에는 bill pugh solution)
Eager Initialization / Static block initialization / Lazy initialization / Thread safe initialization / Double-checked Locking / Bill Pugh Solution / Enum
각 방식에는 공통된 개념을 가지고 있다.
- 다른 클래스에서 클래스의 인스턴스화를 제한하는 private 생성자
- Private static 변수는 동일 클래스의 유일한 인스턴스이다.
- 클래스 인스턴스를 반환하는 public static method 는 외부 세계에서 싱글톤 클래스의 인스턴스를 가져오는 전역 액세스 지점이다.
Eager Initialization
- 클래스 로딩 시 싱글톤 클래스 인스턴스 생성
- 사용되기 전에 인스턴스를 생성하기 때문에 비효율적이다.
- 예외 처리 불가
- 리소스의 양이 작으면 사용 가능하지만 대부분 파일 시스템, db 연결과 같은 리소스에 대해 싱글톤 클래스가 생성된다.
- 클라이언트가 메서드를 호출하지 않는 한 인스턴스화를 피해야 한다.
🔽 EagerInitializedSingleton.java
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton INSTANCE = new EagerInitializedSingleton();
private EagerInitializedSingleton(){}
public EagerInitializedSingleton getInstance() {
return INSTANCE;
}
}
static block initialization
- 클래스 인스턴스가 예외처리 옵션 생성 정적 블록에 생성 되는 것만 제외하면 eager intialization 과 비숫
- 사용되기 전에 인스턴스를 생성하기 때문에 비효율적이다.
🔽 StaticBlockSingleton.java
public class StaticBlockSingleton {
private static StaticBlockSingleton INSTANCE;
private StaticBlockSingleton(){}
// 정적 블록 초기화로 예외 처리
static {
try {
INSTANCE = new StaticBlockSingleton();
} catch (Exception e) {
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static StaticBlockSingleton getInstance() {
return INSTANCE;
}
}
Lazy Initialization
- 전역 접근 메서드로 인스턴스를 생성한다.
- 단일 스레드에서는 잘 작동하지만 멀티 스레드 시스템에서는 이슈가 발생할 수 있다.
➡️ 만약에 다중 스레드가 동시에 if 조건문에 있을 때 , 싱글톤 패턴은 지켜지지 못한다. 다중 스레드에서는 모두 싱글톤 클래스의 서로 다른 인스턴스를 얻게 될 것이다.
🔽 LazyInitializedSingleton.java
public class LazyInitializedSingleton {
private static LazyInitializedSingleton INSTANCE;
private LazyInitializedSingleton(){};
public static LazyInitializedSingleton getInstance(){
if(INSTANCE == null) {
INSTANCE = new LazyInitializedSingleton();
}
return INSTANCE;
}
}
🚨 발생할 수 있는 문제점
만약 스레드 1, 2 가 있을 때 스레드 1이 먼저 if 문을 읽고 조건을 만족하여 진입 하는 순간이라고 한다. (초기화 진행 전) 이 때 스레드 2가 if 문을 진입 (초기화가 진행되지 않아 참) 하여 두 개의 인스턴스가 생성되게 된다.
🔽 LazyInitializedSingleton에서 발생할 수 있는 문제점
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LazyProblem {
private static LazyProblem INSTANCE;
private LazyProblem(){}
public static LazyProblem getInstance(){
if(INSTANCE == null) {
INSTANCE = new LazyProblem();
}
return INSTANCE;
}
public static class Main {
public static void main(String[] args) {
LazyProblem[] lazyProblems = new LazyProblem[10];
// 스레드풀 생성
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int num = i;
service.submit(() -> {
lazyProblems[num] = LazyProblem.getInstance();
});
}
service.shutdown();
while (!service.isTerminated()) {
// 작업이 끝날 때까지 대기
}
for (LazyProblem lazyProblem : lazyProblems) {
System.out.println(lazyProblem.toString());
}
}
}
}
Thread Safe Singleton
- 스레드 세이프 싱글톤 클래스를 만드는 방법은 한 번에 하나의 스레드만 메서드를 접근할 수 있도록 전역 액세스 메서드 동기화
- 스레드의 안전성은 보장하지만 여러개의 모듈들이 매번 객체를 가져올 때 synchronized 메서드를 매번 호출하여 처리 작업에 오버헤드가 발생해서 성능하락이 발생한다.
public class ThreadSafeSingleton {
private static ThreadSafeSingleton INSTANCE;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new ThreadSafeSingleton();
}
return INSTANCE;
}
}
만약 스레드 1이 들어 왔을 때 2~4 가 들어올 수 있기 때문에 경쟁상태(race condition) 이 되지 않도록 이중 잠금(double-checked locking) 을 한다.
🌟 이 때 인스턴스 필드에 volatile 키워드를 붙여줘야 I/O 불일치 문제 해결이 가능한데 JVM 에 대한 이해 & JVM 버전 등 많은 조건이 필요하다. 이러한 이유로 사용을 안 하는 것이 좋다.
🔽 ThreadSafeSingleton.java
public class ThreadSafeSingleton {
private static ThreadSafeSingleton INSTANCE;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new ThreadSafeSingleton();
}
return INSTANCE;
}
public static ThreadSafeSingleton getInstanceUsingDoubleLocking() {
if (INSTANCE == null) {
synchronized (ThreadSafeSingleton.class) {
if (INSTANCE == null) {
INSTANCE = new ThreadSafeSingleton();
}
}
}
return INSTANCE;
}
}
Bill Pugh Singleton ★
- inner static helper class(내부 정적 도움 클래스) 를 사용해서 싱글톤 클래스 생성
- 멀티 스레드에서 안전하고 lazy loading 도 가능한 완벽한 싱글톤 기법
- 클래스 내부에 내부 클래스를 두어 JVM의 클래스 로더 매커니즘과 클래스가 로드되는 시점을 이용한 방법
- static method에서는 static 멤버만 호출 가능하기 때문에 static으로 설정한다.
- 싱글톤 클래스가 로드될 때 SingletonHelper클래스는 메모리에 로드되지 않고 누군가가 메소드를 호출할 때만 getInstance()이 클래스가 로드되어 싱글톤 클래스 인스턴스를 생성
🔽 BillPughSingleton.java
public class BillPughSingleton {
private BillPughSingleton(){}
private static class SingletonHelper {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
🚨 발생할 수 있는 문제점
클라이언트가 싱글톤을 파괴할 수 있다. (Refelction API , 직력화 / 역직렬화)
ENUM ★
- private으로 만들고 한 번만 초기화
- thread safe 한 방법
- 상수 뿐만 아니라, 변수나 메서드를 선언해서 사용이 가능하기 때문에 싱글톤 클래스 처럼 응용 가능
- reflection 을 통한 공격에도 안전
- 일반적인 클래스로 마이그레이션 할 때 처음부터 코드를 짜야 한다.
- 클래스 상속이 필요할 때 클래스 상속이 불가
🔽 EnumSingleton.java
public enum EnumSingleton {
INSTANCE;
public static void doSomething() {
// do something
}
}
참고자료 📖
'KNOWLEDGE' 카테고리의 다른 글
[디자인 패턴] 옵저버 패턴(Observer Pattern) - 자바 예제 (0) | 2024.04.16 |
---|---|
[OS] 페이징, PTBR, TLB(Translation Lookaside Buffer, 변환색인 버퍼) 란 무엇인가? (0) | 2023.12.14 |
[OS] 시스템 호출(System call) & 운영체제 - 커널모드(Kernerl Mode)와 사용자 모드(User Mode) (0) | 2023.11.17 |
[NETWORK] REST API, RESTful 알아보기 / REST란? (1) | 2023.11.16 |
[OS] CPU 스케줄링 알고리즘 / 프로그램 vs 프로세스 vs 스레드 (2) | 2023.11.08 |