일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Navigation
- hilt
- room
- lifecycle
- activity
- sqlite
- LiveData
- onLayout
- CollapsingToolbarLayout
- Android
- 알고리즘
- HTTP
- 안드로이드
- kotlin
- 알림
- Behavior
- ViewModel
- DataBinding
- CoordinatorLayout
- CustomView
- 백준
- notification
- Algorithm
- Coroutine
- onMeasure
- View
- AppBarLayout
- 코틀린
- BOJ
- recyclerview
- Today
- Total
개발일지
Design Pattern in A..Z - Singleton (싱글톤) 본문
Singleton Pattern
객체를 생성할 때 생성자가 호출되고 메모리에 올라가는 등 비용이 발생한다. 만약 객체를 생성할 때 비용이 크다면 객체를 자주 생성하는 일은 시스템에 부담이 클 것이다. 싱글톤 패턴은 객체를 한번만 생성하고 생성된 객체를 재사용하면서 객체의 재생성 비용을 줄이는 디자인 패턴이다.
Database를 연결하고 접근하는 객체를 예로 들어 생각하면 DB를 연결할 때 드는 비용은 매우 비싸다. 하지만 연결을 한번 하고 생성된 객체를 재사용한다면 연결 비용을 줄일 수 있을 것이다.
* 인스턴스화 하는 비용을 줄일 수 있다는 장점을 가지지만, 한번 생성한 인스턴스를 반납하지 않기 때문에 무분별한 싱글톤 패턴 사용은 오히려 메모리 낭비를 일으킨다.
* 싱글톤 패턴은 객체간 결합도를 높이기 때문에 테스트가 어렵고, 멀티 쓰레드 환경에서 동기화 문제를 해결해야한다.
Static vs Singleton
Static을 통해 정적으로 생성된 객체나 함수를 접근할 수 있지만 상속을 통한 확장이 불가능하다.
Singleton 패턴은 상속을 통해 확장할 수 있고, 상속될 수 있어 확장성이 크다.
Eager Initialization (이른 초기화)
static 키워드를 통해 instance를 초기화하여 메모리에 등록해서 사용하는 방식입니다.
static 키워드의 특징은 클래스 로더가 로딩되는 최초 시점에 객체를 생성하고, 이러한 방식은 Thread-Safe합니다. 하지만 클래스 로드할 때 Singleton을 사용 유무에 상관없이 무조건 생성되는 단점이 있습니다.
class MySingleton private constructor() {
companion object {
private val instance = MySingleton()
fun getInstance(): MySingleton {
return instance
}
}
}
Lazy initialization (늦은 초기화)
인스턴스가 null인지 체크하여 필요할 때 한번만 초기화 합니다.
하지만 Multi-Thread 환경에서 getInstace를 동시에 접근할 경우 여러번 생성될 수 있다는 단점을 가지고 있습니다.
class MySingleton private constructor() {
companion object {
private var instance: MySingleton? = null
fun getInstance(): MySingleton {
return instance ?: MySingleton().also {
instance = it
}
}
}
}
Lazy Initialization with synchronized (동기화를 이용한 늦은 초기화)
synchronized를 통해 Locking하여 Thread-Safe를 보장할 수 있다.
하지만 Locking하는 비용이 발생하기 때문에 getInstance()를 호출할 때Multi-Thread 환경에서 성능 저하가 발생할 수 있습니다.
class MySingleton private constructor() {
companion object {
private var instance: MySingleton? = null
@Synchronized
fun getInstance(): MySingleton {
return instance ?: MySingleton().also {
instance = it
}
}
}
}
Lazy Initialization with Double Check Locking (이중 체크를 통한 동기화)
getInstance를 Locking하는 방법이 아닌, instance가 null일 때 Singleton을 Locking하고, instance를 다시 확인하여 객체를 생성한다.
getInstance를 Locking하지 않았기 때문에 Singleton을 한번 생성하면 Locking이 걸리지 않아 성능을 향상 시킬 수 있다.
class MySingleton private constructor() {
companion object {
private var instance: MySingleton? = null
fun getInstance(): MySingleton {
return instance ?: synchronized(this) {
instance ?: MySingleton().also {
instance = it
}
}
}
}
}
Lazy Initialization with Double Check Locking And Volatile
다중 프로세서 환경에서 CPU마다 캐쉬를 가지고 있고 서로 공유되지 않는다. 그렇게 때문에 같은 변수를 접근할 때 어떤 CPU는 Cache Hit가 발생하고 어떤 CPU는 Cache Miss가 발생할 수 있다.
Volatile은 CPU에 캐쉬를 저장하지 않고, Main Memory에 저장하도록 강제하는 방법이다.
class MySingleton private constructor() {
companion object {
@Volatile
private var instance: MySingleton? = null
fun getInstance(): MySingleton {
return instance ?: synchronized(this) {
instance ?: MySingleton().also {
instance = it
}
}
}
}
}
Lazy Initialization with Holder
Synchronized나 Volatile을 사용하지 않기 때문에 성능면에서 유리하며, Class Loader와 static 특성을 이용하여 instace를 호출하지 않는 이상 instance를 생성하지 않으며 Thread-Safe를 보장한다. 제일 많이 사용하는 방식이다.
* static은 Class Loader가 Class를 Load할 때 초기화 된다. instance를 Holder로 감싸면서 Holder를 접근하지 않는 이상 Intance는 생성되지 않으며 Holder를 private으로 선언하여 getInstace를 호출할 때만 접근할 수 있도록 했다.
class MySingleton private constructor() {
private object Holder {
val instance = MySingleton()
}
companion object {
fun getInstance(): MySingleton {
return Holder.instance
}
}
}
'Design Pattern (디자인 패턴)' 카테고리의 다른 글
Design Pattern in A..Z - Command (0) | 2021.10.05 |
---|---|
Design Pattern in A..Z - MVVM (0) | 2021.10.05 |
Design Pattern in A..Z - MVP (0) | 2021.10.05 |
Design Pattern in A..Z - MVC (0) | 2021.10.05 |