개념

싱글톤 패턴이란 클래스의 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대해 Global Access Point를 제공하는 디자인 패턴이다.

  • 싱글톤 패턴은 인스턴스를 하나만 생성하기 때문에 인스턴스를 여러개 생성하는 것보다 메모리 사용에서 이점이 있다.
    • 생성 방식에 따라, 사용하지 않는데 인스턴스를 만들 수도 있기 때문에 메모리 낭비가 발생할 수도 있다.
  • 다른 클래스 간의 데이터 공유가 쉽지만, 동시성 문제가 발생할 수 있다.
  • 싱글톤의 구현 방법은 아래와 같이 여러가지가 존재한다.
    1. Eager Initialization
    1. Lazy Initialization
    1. Thread Safe Lazy Initialization
    1. Double-Checked locking
    1. Initialization on demand holder idiom

코틀린에서는 object 키워드를 사용하면 싱글톤 클래스를 만들 수 있다.

예시

1. Eager Initialization

class Singleton private constructor() {

    companion object {
        private var INSTANCE: Singleton = Singleton()

        fun getInstance(): Singleton {
            return INSTANCE
        }
    }
}

2. Lazy Initialization

class Singleton private constructor() {

    companion object {
        private var INSTANCE: Singleton? = null

        fun getInstance(): Singleton {

            return INSTANCE ?: Singleton().apply {
                INSTANCE = this
            }
        }
    }
}

3. Thread Safe Lazy Initialization

Kotlin의 object 로 선언하면 thread-safe하다.

class Singleton private constructor() {

    companion object {
        private var INSTANCE: Singleton? = null

				@Synchronized
        fun getInstance(): Singleton {
            return INSTANCE ?: Singleton().apply {
                INSTANCE = this
            }
        }
    }
}

4. Double-check locking

class Singleton private constructor() {

		companion object {
				@Volatile private var INSTANCE: Singleton? = null

				fun getInstance() = INSTANCE ?: synchronized(this) {
						INSTANCE ?: Singleton().apply { INSTANCE = this }
				}
		}
}
				
  • 인스턴스 생성 작업만 synchronized로 블럭킹 하는 방법이다.
  • 메소드에 synchronized를 하게되면 호출이 많을 수록 성능이 떨어진다.

5. Initialization on demand holder idiom

자바 코드

public class Something {
    private Something() {}

    private static class LazyHolder {
        static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}
  • JVM에서 Static Inner Class는 실행되는 시점에 초기화된다.
  • 또한 클래스 초기화는 순차적으로 일어나는 것을 보장하기 때문에 동시성 문제가 발생하지 않는다.

코틀린으로 한다면 이런 느낌일 거 같다.

class Singleton private constructor() {
    companion object {
        class LazyHolder private constructor() {
            companion object {
                var INSTANCE = Singleton()
            }
        }
        fun getInstance() = LazyHolder.INSTANCE
		}
}

장단점

장점

  • 클래스의 인스턴스를 하나만 가진다.
  • Global Access Point를 얻는다.
  • 필요한 시점(처음 호출되는)에 초기화 한다.

단점

  • SRP(단일 책임 원칙)위반
    1. 클래스에 인스턴스가 하나만 있도록 함
    1. 해당 인스턴스에 대한 Global Access Point 제공
    • 위 두가지 책임을 동시에 가지고 있다.
  • 다중 스레드 환경에서 동시성 문제가 발생할 수 있다.
    • 여러 쓰레드가 동시에 싱글톤 객체를 생성하여 인스턴스가 여러 개 생길 수도 있다.
  • 유닛 테스트가 어렵다.
    • Mock 객체를 만들 때 상속을 이용하는데 싱글턴은 상속을 할 수 없기 때문


Uploaded by N2T

+ Recent posts