# [Design Pattern] 싱글톤 패턴 (w. Java, Kotlin)

## **들어가기 전에**

---

**개발자라면 한 번쯤 들어봤을 법한 싱글톤 패턴이란 무엇일까?**  
기술면접을 가면 꼭 한 번씩 물어보는 질문일 것이다.  
 

> 면접관 : 디자인 패턴에 대해서 알고 있나요?  
> 나 : 네, 깊게는 아니지만 간단한 개념정도는 알고 있습니다.  
> 면접관 : 그럼 싱글톤 패턴이 뭐죠?  
> 나 : 인스턴스를 오직 한 개만 제공하도록 하는 디자인 패턴입니다.  
> 면접관 : 이걸 왜 쓸까요?

   
위와 같은 면접 흐름에서 많이도 뒤통수를 맞아본 기억이 많다.  
Spring 에서는 가장 핵심적인 원리라고 하는데 나는 그 부분이 잘 와닿지 않았다.  
개발자로서 늘 **왜?** 라는 물음과 함께 지내는데 공식문서를 열심히 찾아봐도 블로그를 열심히 뒤져도 그런 내부 코드는 확인할 수 없었다.  
실제로 코드로 봐야 아, 하고 와닿는 부분이 있을 텐데 그 부분을 깊게 파보다가 결국엔 일단 개념을 외우고 나중에 천천히 뜯어보자라는 생각을 품었다.(<s>이때 뜯어봤어야 했는데....) 그래서 지금이라도 이렇게 뜯어보려고 한다.</s>  
   
각설하고 이 포스팅에서는 아래와 같은 내용을 다룰 것이다.

> 싱글톤 패턴은 하나의 객체만 생성해서 해당 객체만을 공유할 수 있도록 하는 디자인 패턴이다.  
> 하지만 모든 클래스나 구조체마다 싱글톤 패턴을 적용하기엔 무리가 있을 수 있다.  
> 따라서 Java 와 Kotlin 에서 싱글톤 패턴을 어떻게 구현하는지 알아보고 상황에 맞게 적절하게 활용하도록 하자.그리고 Spring 에서는 어떻게 활용되는지까지 고민한 흔적을 기록한다.

## **언제 싱글톤 패턴이 필요할까?**

---

백기선님이 강의 중에 싱글톤 패턴에 대해서 이렇게 말씀하셨다.  
 

> 시스템 런타임, 환경 세팅에 대한 정보 등 인스턴스가 여러 개일 때 문제가 생길 수 있는 경우가 있다.  
> 이때 인스턴스를 오직 한 개만 만들어서 제공하는 클래스가 필요하다.

**싱글톤 패턴은 간단하게는 인스턴스를 오직 한 개만 제공하는 클래스이다.**

그렇다면 대체 인스턴스가 한 개 필요한 순간이 언제일까?  
나는 이 질문에 대한 대답을 Spring을 기준으로 먼저 생각했다.  
   
우선 @Entity, @Service, @Repository, @Controller 와 같은 어노테이션이 붙은 클래스는 싱글톤 패턴이어야 할 것이다. 멀티스레드 환경에서 사용자의 숫자마다 각자 제공하는 DB 데이터 환경이나 서비스 로직이 다르다면 그것도 말이 안 되지 않을까?  
**단순하게 언제 어디서나 사용자가 수십, 수백만이 되어도 같은 것을 제공해야만 하는 클래스에 적용해야만 하는 것이다.**  
   
이게 아마 Spring 에서는 대표적으로 @Bean, @Component 이 붙은 클래스나 메소드라면 전부 적용될 것이다.  
Spring 프로젝트가 실행될 때 스프링 컨테이너에 해당 어노테이션이 붙은 클래스나 메소드의 구현체가 등록되며 해당 빈에 대한 요청이 있을 때마다 스프링 컨테이너는 이 인스턴스를 제공하는 방식이기 때문이다.

| **Singleton** |
| --- |
| \- instance : Singleton |
| \+ getInstanace() : Singleton |

  
 

## **Java에서 구현하는 법**

> Java 코드는 수업 자료 중 하나이므로 공개하지는 않겠다. 대신 아래에 kotlin 으로 바꾼 코드를 제시한다.

#### **가장 단순히 구현하는 법**

---

클래스 안에서 private 한 인스턴스 변수를 생성한 후 static 메소드를 통해 객체를 생성하려는 순간, 객체가 비어있다면 새로 생성하고 그렇지 않다면 클래스 안에 있는 인스턴스 변수를 전달한다.  
   
단점은 thread safe 하지 않는다는 점이다.  
동시에 다른 thread 에서 빈 인스턴스 변수에 접근하려고 했을 때 어떻게 될 지 고민해보자.  
 

```java
/**
 * [가장 단순히 구현하는 법]
 * - 클래스 안에서 private 한 인스턴스 변수를 생성한 후 static 메소드를 통해 객체를 생성하려는 순간, 객체가 비어있다면
 * 새로 생성하고 그렇지 않다면 클래스 안에 있는 인스턴스 변수를 전달한다.
 * -  thread safe 하지 않다.
 * - 동시에 다른 thread 에서 빈 인스턴스 변수에 접근하려고 했을 때 어떻게 될지?
 */
class Settings2 private constructor() {
    companion object {
        private var instance: Settings2? = null

        fun getInstance(): Settings2 {
            if (instance == null) {
                instance = Settings2()
            }

            return instance!!
        }
    }
}
```

```java
@Test
    @DisplayName("싱글톤 패턴이 적용되지 않아 같은 클래스로 만든 인스턴스는 서로 다르다")
    fun singletonBySettingsTest() {
        val basicSettings = BasicSettings()
        val basicSettings2 = BasicSettings()

        assertTrue(basicSettings != basicSettings2)
    }

    @Test
    @DisplayName("private 생성자에 static 메소드로 생성자를 전달해줘도 인스턴스는 서로 다르다")
    fun singletonBySettings1Test() {
        val settings1 = Settings1.getInstance()
        val settings2 = Settings1.getInstance()

        assertTrue(settings1 != settings2)
    }

    @Test
    @DisplayName("static 인스턴스를 생성해서 객체가 null 이면 한번 생성하고 그 이후엔 그 객체를 유지해서 싱글톤 패턴을 유지한다")
    fun singletonBySettings2Test() {
        val settings1 = Settings2.getInstance()
        val settings2 = Settings2.getInstance()
        assertTrue(settings1 === settings2)
    }
```

   
 

#### **멀티 쓰레드 환경에서 안전하게 구현하는 방법**

---

* synchoronized 키워드를 사용한다. 그러나 동기화 처리 작업 때문에 늦어질 수 있다.
    

```java
/**
 * [멀티 쓰레드 환경에서 안전하게 구현하는 방법]
 * - synchronized 사용하기
 */
class Settings3 private constructor() {
    companion object {
        private var instance: Settings3? = null

        @Synchronized
        fun getInstance(): Settings3 {
            return instance ?: Settings3().also { instance = it }
        }
    }
}
```

```java
    @Test
    @DisplayName("Settings3 클래스에서 멀티 스레드 환경일 때 객체가 일치하는지 확인한다")
    fun singletonBySettings3Test() {
        val instances: MutableSet<Settings3> = ConcurrentHashMap.newKeySet()
        val threadCount = 100

        val threads = mutableListOf<Thread>()

        for (i in 1..threadCount) {
            val thread = Thread {
                val instance = Settings3.getInstance()
                instances.add(instance)
            }
            threads.add(thread)
            thread.start()
        }

        for (thread in threads) {
            thread.join()
        }

        assertEquals(1, instances.size)
    }
```

* 이른 초기화(eager initialization)
    

```java
/**
 * [멀티 쓰레드 환경에서 안전하게 구현하는 방법]
 * - 이른 초기화 사용
 */
class Settings4 private constructor() {
    companion object {
        private val instance: Settings4 = Settings4()

        fun getInstance(): Settings4 = instance
    }
}
```

```java
    @Test
    @DisplayName("이른 초기화(eager initialization) 사용하기")
    fun singletonBySettings4() {
        val settings = Settings4.getInstance()
        assertTrue(settings === Settings4.getInstance())
    }
```

* doublue checked locking
    

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

        fun getInstance(): Settings5 {
            return instance ?: synchronized(this) {
                instance ?: Settings5().also { instance = it }
            }
        }
    }
}
```

```java
    @Test
    @DisplayName("double checked locking 사용하기")
    fun singletonBySettings5Test() {
        val settings1 = Settings5.getInstance()

        assertTrue(settings1 === Settings5.getInstance())
    }
```

   
   
 

* static inner 로 구현하기
    

```java
/**
 * [멀티 쓰레드 환경에서 안전하게 구현하는 방법]
 * - static inner 클래스 사용하기
 * (권장하는 방법)
 */
class Settings6 private constructor() {

    companion object {
        private object SettingsHolder {
            val instance = Settings6()
        }

        fun getInstance(): Settings6 = SettingsHolder.instance
    }
}
```

```java
    @Test
    @DisplayName("static inner 클래스 사용하기")
    fun singletonBySetting6Test() {
        val settings1 = Settings6.getInstance()

        assertTrue(settings1 === Settings6.getInstance())
    }
```

   
   
**자, 그러면 이 싱글톤 패턴 구현 방법을 깨뜨리려면 어떻게 해야할까?**  
   
reflection 을 사용하면 된다!  
 

```java
    @Test
    @DisplayName("싱글톤 패턴 구현 방법을 깨트리는 방법(리플렉션)")
    fun breakSingleton() {
        // given
        val settings = Settings6.getInstance()

        val constructor = Settings6::class.primaryConstructor!!
        constructor.isAccessible = true

        // when
        val settings2 = constructor.call()

        // then
        assertTrue(settings != settings2)
    }
```

   
   
**그럼 가장 찬란하고 아름다우며 단순하고 안전한 싱글톤 패턴 구현 방법은 뭘까?**  
**바로 enum 이다.**  
 

```java
enum class Settings {
    INSTANCE,
}
```

```java
    @Test
    @DisplayName("싱글톤 패턴 구현(ENUM 과 reflection)")
    fun singletonByEnumTest() {
        // given
        val settings = Settings.INSTANCE

        // when
        val enumClass = Settings::class
        val enumProperties = enumClass.declaredMemberProperties

        // then
        enumProperties.forEach { property ->
            property.isAccessible = true
            val settings1: Any? = property.get(settings) // Any? 타입으로 선언

            assertTrue(settings === settings1)
        }
    }
```

   
   
 

### **Kotlin 으로 싱글톤 패턴 구현하기**

#### **객체 선언(Object Declaration)**

---

```java
/**
 * [코틀린에서 싱글톤 패턴 구현하기]
 * [ V ] 객체 선언(Object Declaration)
 *
 * 가장 권장하는 방법
 *
 * 클래스 정의와 동시에 초기화되는 단일 인스턴스로 동작한다.
 * 이 객체 선언을 통해 싱글톤 패턴을 쉽고 간결하게 구현할 수 있다.
 *
 * object 는 싱글톤 객체 선언을 위한 전용 키워드
 *
 * 생성자 생성 불가능, 상속 불가능, 추상화 불가능, 인터페이스 구현 불가능
 */
object ObjectDeclarationSingletonUser
```

```java
@Test
    @DisplayName("객체 선언으로 싱글톤 패턴을 구현할 수 있다")
    fun objectDeclarationTest() {
        val user = ObjectDeclarationSingletonUser
        val user2 = ObjectDeclarationSingletonUser

        assertTrue(user === user2)
    }
```

#### **패키지 수준 함수(Package-Level Function)**

---

```java
/**
 * [코틀린에서 싱글톤 패턴 구현하기]
 * [ V ] 패키지 수준 함수(Package-Level Function)
 */
private var instance: PackageLevelSingletonUser? = null

fun getPackageLevelSingletonInstance(): PackageLevelSingletonUser {
    return instance ?: synchronized(PackageLevelSingletonUser::class) {
        instance ?: PackageLevelSingletonUser().also { instance = it }
    }
}

class PackageLevelSingletonUser() {
    // 클래스의 내용
}
```

```java
    @Test
    @DisplayName("패키지 수준 함수로 싱글톤 패턴을 구현할 수 있다")
    fun packageLevelFunctionTest() {
        val user = getPackageLevelSingletonInstance()

        assertTrue(user === getPackageLevelSingletonInstance())
    }
```

   
 

#### **동반 객체(Companion Object)**

---

```java
/**
 * [코틀린에서 싱글톤 패턴 구현하기]
 * [ V ] 동반 객체(Companion Object)
 */
class CompanionObjectSingletonUser private constructor() {
    companion object {
        private val instance = CompanionObjectSingletonUser()

        fun getInstance(): CompanionObjectSingletonUser = instance
    }
}
```

```java
    @Test
    @DisplayName("동반 객체로 싱글톤 패턴을 구현할 수 있다")
    fun companionObjectTest() {
        val user = CompanionObjectSingletonUser.getInstance()

        assertTrue(user === CompanionObjectSingletonUser.getInstance())
    }
```

   
 

#### **lazy 초기화**

---

```java
/**
 * [코틀린에서 싱글톤 패턴 구현하기]
 * [ V ] lazy 초기화
 * lazy 를 이용해서 instance 생성을 지연 초기화하며 처음 접근되는 시점에서 초기화 된다
 * 그 이후에는 초기화된 값을 재사용한다
 */
class LazyInitializationSingletonUser private constructor(
    var firstName: String? = null,
    var lastName: String? = null,
) {
    companion object {
        private val singletonUser by lazy(LazyThreadSafetyMode.PUBLICATION) { LazyInitializationSingletonUser() }
        fun getInstance(): LazyInitializationSingletonUser = singletonUser
    }
}

fun main(args: Array<String>) {
    with(LazyInitializationSingletonUser.getInstance()) {
        firstName = "Ihor"
        lastName = "Kuchen"
    }
}
```

```java
    @Test
    @DisplayName("lazy 초기화를 사용한 싱글톤 패턴 테스트")
    fun lazyInitializationTest() {
        val user = LazyInitializationSingletonUser.getInstance()

        assertTrue(user === LazyInitializationSingletonUser.getInstance())
    }
```

   
 

### **더 고민해야할 점**

---

* 싱글톤 패턴 복습
    
    * 자바 enum 을 사용하지 않고 싱글톤 패턴을 구현하는 방법은?
        
    * private 생성자와 static 메소드를 사용하는 방법의 단점은?
        
    * enum을 사용해 싱글톤 패턴을 구현하는 방법의 장점과 단점은?
        
    * static inner 클래스를 사용해 싱글톤 패턴을 구현해보자
        
* 실무에서는 어떻게 쓰이나?
    
    * 스프링에서 빈의 스코프 중 하나로 싱글톤 스코프
        
    * 자바 java.lang.Runtime
        
    * 다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리 등) 구현체의 일부로 쓰이기도 한다
