👩🏻‍💻 프로그래밍/C#

싱글톤을 구현하는 2가지 방법 - Lazy Initialization (지연 초기화), Static Initialization (정적 초기화)

뽀도 2024. 6. 21. 11:39

 

코드 보다보니까 어떤 사람은 싱글톤을 Lazy Initialization (지연초기화), Static Initialization (정적 초기화)로 나눠서 하니까 두개의 차이가 뭐가 있나 궁금해서 찾아보았다. 


1. Lazy Initialization (지연초기화)

 

◈ 특징 

- 인스턴스가 실제로 필요할 때까지 초기화를 미룸. 

- 처음 접근할 때 인스턴스를 생성하므로 메모리를 효율적으로 사용할 수 있음.

- Lazy 클래스나 double-checked locking 등을 사용하여 구현할 수 있음.

- 초기화 순서와 타이밍을 명확하게 제어할 수 있음. 

 

장점 

1. 메모리 효율성 : 인스턴스를 실제로 필요할 때 까지 생성하지 않으므로 불필요한 메모리 사용을 방지함.

2. 초기화 제어 : 클래스가 로드 될 때가 아닌, 실제로 필요할 때 인스턴스를 생성하므로 초기화 시점을 제어할 수 있음. 

3. Lazy<T> 사용시 간결한 코드 : .NET `Lazy<T>` 클래스를 사용하면 간결하고 안전하게 지연 초기화를 구현할 수 있음 

 

단점 


1. 약간의 성능 오버헤드 : Lazy클래스나 double-checked locking 구현 방식은 약간의 성능 오버헤드를 초래할 수 있음 


1-1. Double checked locking : 락 획득, 메모리 배리어, 조건 체크로 인한 성능 오버헤드가 발생할 수 있음 

1-2. Lazy<T> 클래스 : 내부 동기화, 추가 객체 생성, 함수 호출 오버헤드로 인해 약간의 성능 저하 

※ 실제 영향 : 대부분의 애플리케이션에서는 이러한 오버헤드가 매우 미미하고 싱글톤 패턴의 이점이 오버헤드보다 훨 씬 더 큼

더보기

double-checked locking

 

double-checked locking은 초기화 시점에서의 스레드 충돌을 방지하기 위해 두 번의 체크를 수행하는 기법
처음에는 잠금 없이 인스턴스가 null인지 확인하고, null인 경우에만 동기화 블록을 사용해 인스턴스를 생성 


 

2. 복잡성 : 일반적인 정적 초기화 방식보다 구현이 복잡 할 수 있음. 

 

예제코드 - LazyInstance

public class Singleton
{
    private static readonly Lazy<Singleton> lazyInstance = new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance
    {
        get { return lazyInstance.Value; }
    }

    private Singleton() { }
}

 

예제코드 - Double Checked Locking 

public class Singleton
{
    private static Singleton instance = null;
    private static readonly object lockObj = new object();

    // 생성자는 private으로 선언하여 외부에서 인스턴스 생성을 막음
    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock (lockObj)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

 

 

2. Static Initialization (정적 초기화)

 

◈ 특징 

- 클래스가 처음 로드될 때 정적 초기화 블록에서 인스턴스를 생성함.

- 정적 필드 초기화 방식으로 구현

- JVM이나 .NET 런타임이 클래스 로드 시점에 스레드 안전성을 보장함. 

- 애플리케이션 시작시 인스턴스가 생성되므로, 인스턴스가 항상 존재하는것이 보장됨 

 

◈ 장점 

- 단순성 : 구현이 간단하고 직관적

- 성능 : 초기화 과정에 추가적인 성능 오버헤드가 없음.

- 스레드 안전성 : 클래스 로드 시점에 초기화 되므로 스레드 안정성을 보장함

 

◈ 단점

- 초기 메모리 사용 : 클래스가 로드 될 때 인스턴스를 생성하므로 초기 메모리 사용이 많을 수 있음.

- 초기화 제어 어려움 : 클래스 로드 시점에 인스턴스가 생성되므로 초기화 시점을 제어하기 어려움 

 

public class Singleton
{
    private static readonly Singleton instance = new Singleton();

    public static Singleton Instance
    {
        get { return instance; }
    }

    private Singleton() { }
}

 

 

◈ 비 교

특성 Lazy Initialization Static Initialization
초기화 시점 인스턴스가 처음 필요할 때(즉, 첫번째 호출 시점)

리소스를 효율적으로 사용할 수 있고, 필요할때만 인스턴스 생성 
클래스가 처음 로드될 때
(즉, 애플리케이션이 시작될때)
메모리 효율성 필요할 때까지 인스턴스를 생성하지 않음 클래스 로드 시 인스턴스를 생성함
구현 복잡성 약간 더 복잡할 수 있음 구현이 간단하고 직관적임
성능 약간의 성능 오버헤드가 있을 수 있음 추가적인 성능 오버헤드 없음
초기화 제어 초기화 시점을 명확히 제어 가능 초기화 시점 제어 어려움
스레드 안전성 Lazy<T>나 double-checked locking 사용 런타임이 스레드 안전성을 보장