본문 바로가기
프로그래밍/C#

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

by 뽀도 2024. 6. 21.

 

코드 보다보니까 어떤 사람은 싱글톤을 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 사용 런타임이 스레드 안전성을 보장

 

반응형

댓글