본문 바로가기
Computing and DB 🖥/Computer Science

[디자인 패턴] 싱글톤 제 1장 - "개념 및 멀티 쓰레드와 이른 초기화"

by dudefromkorea 2024. 1. 24.

 

전역으로 공통되어야 하는 데이터가 담긴 인스턴스를 여러 개 생성한다면?

 

 

예를 들어 GlobalSettings 라는 클래스가 존재하고

PersonalSettings 에서 GlobalSettings 의 인스턴스를 여러 개 사용한다면

인스턴스의 일관성을 보장할 수 없고, 자원 낭비, 동기화 문제 등 여러 문제가 발생합니다

(GlobalSettings 는 글로벌 세팅으로 각 객체의 독립성이 요구되지 않는다는 가정)

문제가 되는 상황 예시 

public class PersonalSettings {
    GlobalSettings gloSetOne = new GlobalSettings();
    GlobalSettings gloSetTwo = new GlobalSettings();
        
    boolean equalCheck = (gloSetOne == gloSetTwo); // false
}

 

서로 다른 인스턴스인 근본적인 이유는 무엇일까요?

바로 "new 연산자" 때문입니다

 

그래서 제일 먼저 싱글톤 패턴을 구현하기 위해서는

new 연산자를 이용한 인스턴스의 생성을 제한해야 합니다

new 연산자 제한하기

public class GlobalSettings {
    private GlobalSettings() {}
}

 

기본 생성자를 private 으로 선언하여 클래스 외부에서

인스턴스 생성이 불가능하도록 구현하였습니다

 

생성자가 private 이기 때문에 이제 인스턴스 생성은

오직 클래스 내부에서만 가능합니다

private 생성자로 인스턴스를 생성하기 위한 getInstance 메소드

public class GlobalSettings {
    private GlobalSettings() {}
    
    public static GlobalSettings getInstance() {
        return new GlobalSettings();
    }
}

 

그러면 이제 인스턴스를 생성할 때는

아래 예시처럼 생성하게 됩니다

getInstance 에 접근하여 인스턴스 생성하기

public class PersonalSettings {
    GlobalSettings gloSetOne = GlobalSettings.getInstance();
    GlobalSettings gloSetTwo = GlobalSettings.getInstance();
        
    boolean equalCheck = (gloSetOne == gloSetTwo); // false
}

 

그럼에도 불구하고 이전과 동일하게

두 개의 다른 인스턴스가 생성되는 것을 확인할 수 있습니다

 

getInstance 메소드 내부에서 여전히 new 연산자를

사용하고 있기 때문에 예상된 결과입니다

 

이를 해결하기 위해 인스턴스가 존재하지 않을 경우에만

new 연산자를 사용하도록 로직을 구현합니다

 

늦은 초기화 (lazy initialization)

오직 하나의 인스턴스를 생성하기 위한 방안

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

 

인스턴스의 null 체크 로직이 추가된 GlobalSettings 를 활용하여

gloSetOne 과 gloSetTwo 를 생성할 경우 둘은 같은 객체를 참조하게 됩니다

 

하지만 해당 코드가 멀티 쓰레드 환경에서 동작한다면?

무용지물이 될 것입니다

 

thread1, thread2 가 있다고 가정해 봅시다

thread1 이 if 문을 통과하여 new 연산자로 생성하려는 순간에

thread 2 또한 if 문을 통과할 수도 있습니다

 

따라서 멀티 쓰레드 환경에서

thread - safe 하게 구현하기 위해서는

synchronized 키워드가 필요합니다

synchronized 를 활용한 싱글톤 예시

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

 

이렇게 synchronized 키워드를 추가하여 구현하게 되면

멀티 쓰레드 환경에서도 thread - safe 하게 싱글톤을 보장할 수 있지만

성능 관련 이슈가 발생할 우려가 나타납니다

 

JAVA 에서 권장하는 enum 으로 구현하기, static enum class 로 holder 사용하기 등등

여러 가지 방법들이 존재하지만 오늘은 간단하게 늦은 초기화와

상반되는 개념인 이른 초기화에 대해 알아보도록 하겠습니다

 

 

이른 초기화 (eager initialization)

객체를 꼭 나중에 만들어야 하지 않거나, 객체를 생성하는데 큰 비용이 들지 않는다는

전제 조건을 바탕으로 미리 인스턴스를 생성해서 초기화시키는 방법

이른 초기화 (eager initialization) 예시

public class GlobalSettings {
    private static final GlobalSettings INSTANCE = new GlobalSettings();
    private GlobalSettings() {}
    
    public static GlobalSettings getInstance() {
        return INSTANCE;
    }
}

 

 

이른 초기화의 전제 조건 사항들이 이른 초기화라는 개념의

단점이자 주의해야 할 점이라고 볼 수 있겠습니다

 

사용이 아직 불확실한 인스턴스를 만드는 과정이 메모리를 많이 사용한다면

인스턴스를 생성하는 것 자체가 단점으로 작용할 수 있습니다

 

따라서 이를 보안하기 위해 여러 방안들이 존재하는데

이는 다음 시간에 다뤄보도록 하겠습니다

 

싱글톤 패턴 제 2장 보러 가기

 

728x90
반응형