[디자인패턴] Singleton Pattern, 싱글톤 패턴

Chloe Choi·2021년 2월 4일
0

디자인패턴

목록 보기
4/11

Why

  • 인스턴스가 절대적으로 한 개만 존재하는 것을 보증해야 하는 경우
  • 매번 새로운 객체를 생성하는 것이 객체 로딩 시간으로 인해 성능이 안 좋아지는 경우

How

고정된 메모리 영역을 얻어 한 번의 new로 인스턴스를 사용하자!

What

싱글톤 패턴이란,

어떤 클래스가 최초 한 번만 메모리를 할당하고 그 메모리에 인스턴스를 만들어 사용하는 디자인 패턴

스레드풀, 캐시, 대화상자, 로그기록 객체 등에 사용되는 패턴입니다.

장점

  • 메모리 낭비 방지
  • 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스로, 다른 클래스 인스턴스 간 데이터 공유가 쉬워짐

단점

  • 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터 공유에 사용된다면, 클래스 간 결합도가 높아지며 수정이 어려워져 OCR에 위배
  • 제대로 GC가 되지 않는다면 끝까지 메모리 위에

따라서, 적절히 사용하는 것이 매우 중요한 패턴입니다!

구현

Kotlin

object 키워드를 제공합니다. 이를 통해 최초 호출 시 생성되도록 합니다.

Java

여러 방법이 있는데요, 하나하나 보며 어떤 방법이 가장 나은지 설명하겠습니다.
Eager initialization

class Singleton {
	private static Singleton instance = new Singleton();
    
    private Singleton() {}
    public static Singleton getInstance() {return instance();}
}

이 방식은 해당 instance에 접근하지 않아도 생성되어 memory leak을 유발한다는 단점을 갖고 있습니다.
Lazy initialization

class Singleton {
	private static Singleton instance;
    
    private Singleton() {}
    public static Singleton getInstance() {
    	if (instance == null) instance = new Singleton();
        return instance;
    }
}

getInstance() 메소드가 호출되어야 초기화를 합니다. 따라서, 위 Eager initialization 방식의 단점을 보완한 것을 확인할 수 있습니다. 하지만 멀티 스레드 환경에서 문제가 없을까요? 동기화 처리가 없어서 두 개 이상의 인스턴스를 생성할 수 있습니다.

class Singleton {
	private static Singleton instance;
    
    private Singleton() {}
    public static synchrozied Singleton getInstance() {
    	if (instance == null) instance = new Singleton();
        return instance;
    }
}

synchrozied 키워드를 사용해 thread-safe하게 동작할 수 있습니다. 하지만 여러 곳에서 공유하라고 싱글톤을 쓰는건데 단일 스레드처럼 동작하는게 비효율적입니다! instance = new Singleton()이 유일한 호출이므로 해당 부분으로 동기화를 최소화 해 더 효율적으로 만들 수 있을거 같습니다!

class Singleton {
	private static Singleton instance;
    
    private Singleton() {}
    public static Singleton getInstance() {
    	synchrozied(Singleton.class) {
    		if (instance == null) instance = new Singleton();
        }
        return instance;
    }
}

static synchronized block을 이용해 동기화 범위를 최소화 했습니다. 이를 통해 synchronized에 의한 성능 저하를 완화했습니다.

이론으로는 완벽해 보입니다. 하지만 자바 플랫폼 메모리 모델에서는 완벽한 작동을 보장할 수 없습니다. 문제가 되는 상황은 다음과 같습니다.

if문 내의 메모리 할당 부분은 #1. 싱글톤 객체를 위한 메모리할당, #2. 메모리를 할당 받아 null이 아닌 상태가 됨. 하지만 아직 초기화 되지 않음. #3. 싱글톤 생성자 실행 의 흐름을 갖습니다. #2까지 진행되고 스레드2가 선점한다면 스레드2는 half initialized state의 instance에 접근하게 됩니다.

이 문제는 volatile 키워드로 해결할 수 있습니다!

class Singleton {
	private static volatile Singleton instance;
    
    private Singleton() {}
    public static Singleton getInstance() {
    	synchrozied(Singleton.class) {
    		if (instance == null) instance = new Singleton();
        }
        return instance;
    }
}

volatile keyword: Java 변수를 main memory에 저장하겠다는 것을 명시. 키워드 사용 시 JVM에 의해 재배치 되지 않는다는 "Happens-before"을 보장

따라서, 동기화로 인한 성능저하를 최소로 하고 thread-safe 한 싱글톤 클래스를 구현해 봤습니다 !!🤓

🙇🏻‍♀️

clone of singleton object
https://www.geeksforgeeks.org/prevent-singleton-pattern-reflection-serialization-cloning/

static vs singleton
https://stackoverflow.com/questions/519520/difference-between-static-class-and-singleton-pattern

profile
똑딱똑딱

1개의 댓글

comment-user-thumbnail
2021년 5월 4일

Java의 synchronized block은 visibility를 보장한다. 따라서, synchronized block 안에서는 volatile과 같은 처리가 필요하지 않다. 특히 위 코드에서는 read도 synchronized block 안에서 진행하기 때문에 volatile 키워드가 필요하지 않음
ref. https://stackoverflow.com/questions/3214938/java-volatile-modifier-and-synchronized-blocks

read 를 밖으로 뺀다면? -> volatile, double checking 필요
class Singleton {
private static volatile Singleton instance;

private Singleton() {}
public static Singleton getInstance() {
    if (instance == null) {
        synchronized(Singleton.class) {
            if (instance == null) instance = new Singleton();
        }
    return instance;
}

}

답글 달기