Safety Singleton pattern

Exploring the concept of the Singleton pattern and its implementation in Kotlin

September 10, 2024


싱글턴 패턴이란?

  • 클래스의 인스턴스가 오직 하나만 존재합니다.
  • 이 인스턴스에 대한 전역적인 접근점을 제공합니다.
  • 싱글턴은 리소스 공유, 설정 관리, 데이터베이스 연결 등에 자주 사용됩니다.

멀티스레드 환경에서의 문제점

멀티스레드 환경에서 단순한 싱글턴 구현은 문제를 일으킬 수 있습니다. 여러 스레드가 동시에 인스턴스를 생성하려 할 때, 의도치 않게 여러 인스턴스가 생성될 수 있기 때문입니다.

다음은 코틀린에서 멀티스레드 안전한 싱글턴을 구현하는 방법입니다.

class Singleton private constructor() {
    companion object {
        @Volatile
        private var instance: Singleton? = null

        fun createInstance() = instance ?: synchronized(this) {
            instance ?: Singleton().also { instance = it }
        }
    }
}
  • private 생성자: private constructor()를 사용하여 외부에서 직접 인스턴스를 생성하는 것을 방지합니다.
  • companion object: 코틀린에서 정적 멤버를 구현하기 위해 사용됩니다.
  • @Volatile: 이 어노테이션은 instance 변수가 모든 스레드에서 항상 최신 값을 읽도록 보장합니다.
  • Double-Checked Locking: createInstance() 메서드에서 사용된 이 패턴은 성능을 최적화하면서도 스레드 안전성을 보장합니다.

구현 설명

  1. 먼저 instance가 null인지 확인합니다. (instance ?:)
  2. null이 아니면 바로 그 인스턴스를 반환합니다.
  3. null이면 synchronized 블록에 진입합니다.
  4. 동기화 블록 내에서 다시 한 번 null 체크를 합니다. (Double-Checked Locking)
  5. 여전히 null이면 새 인스턴스를 생성하고 instance에 할당합니다.

이 방식은 첫 번째 null 체크로 대부분의 경우 동기화 오버헤드를 피하면서, 동시에 여러 스레드가 인스턴스를 생성하는 것을 방지합니다.

fun main() {
val singleton1 = Singleton.createInstance()
val singleton2 = Singleton.createInstance()

    println(singleton1 === singleton2)  // true 출력
    singleton1.print()  // "Singleton" 출력
}