배경
코틀린의 Lazy Init의 경우,
멀티쓰레드 환경에서의 안정성을 위해
Synchronization 옵션을 제공한다
Synchronization 옵션
코틀린에서는 Synchronization 옵션 3가지를 제공한다
- Synchronized
- Publication
- None
아래 예제와 같이 원하는 Synchronazation 옵션을 설정가능하다
val|var varName : T by lazy({Synchronization 옵션}){
//초기화 구문
}
예)
val name : String by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
//초기화 구문
}
코틀린에서는 언어 자체를 뜯어볼 수 있다
lazy Init을 자세히 살펴보면 아래와 같이 옵션에 따라 분기하게 된다
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
//...
}
각 옵션에 따른 특징들은 아래와 같다
Synchronized(Default)
초기화 관련 로직이 딱 한번 수행될 수 있지만
맨 처음에 반환된 값 만이 초기화 값으로 쓰인다
예제코드)
import java.time.LocalDateTime
class HelloBot{
val greeting: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
println("초기화 수행")
getHello()
};
fun sayHello() = println(greeting);
}
fun getHello() = "안녕하세요 현재시간은 " + LocalDateTime.now() + " 입니다."
fun main(){
val helloBot = HelloBot()
for(i in 1 until 5) {
Thread{
helloBot.sayHello();
}.start();
}
}
결과)
초기화 로직은 딱 한번 수행되고,
초기화 된 값도 동일한 것을 볼 수 있다
초기화 수행
안녕하세요 현재시간은 2023-10-30T22:02:53.755486 입니다.
안녕하세요 현재시간은 2023-10-30T22:02:53.755486 입니다.
안녕하세요 현재시간은 2023-10-30T22:02:53.755486 입니다.
안녕하세요 현재시간은 2023-10-30T22:02:53.755486 입니다.
Lazy Init과 관련된 소스코드를 자세히 살펴보면 아래와 같다
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
/* synchronized 키워드로 Thread-Safe를 보장 */
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
Publication
초기화 관련 로직이 여러 번 수행될 수 있지만
맨 처음에 반환된 값 만이 초기화 값으로 쓰인다
예제코드)
import java.time.LocalDateTime
class HelloBot{
val greeting: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
println("초기화 수행")
getHello()
};
fun sayHello() = println(greeting);
}
fun getHello() = "안녕하세요 현재시간은 " + LocalDateTime.now() + " 입니다."
fun main(){
val helloBot = HelloBot()
for(i in 1 until 5) {
Thread{
helloBot.sayHello();
}.start();
}
}
결과)
초기화 로직은 여러 번 수행되지만,
초기화 된 값은 동일한 것을 볼 수 있다
초기화 수행
초기화 수행
초기화 수행
초기화 수행
안녕하세요 현재시간은 2023-10-30T22:01:07.133936 입니다.
안녕하세요 현재시간은 2023-10-30T22:01:07.133936 입니다.
안녕하세요 현재시간은 2023-10-30T22:01:07.133936 입니다.
안녕하세요 현재시간은 2023-10-30T22:01:07.133936 입니다.
Lazy Init과 관련된 소스코드를 자세히 살펴보면 아래와 같다
private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
@Volatile private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// this final field is required to enable safe initialization of the constructed instance
private val final: Any = UNINITIALIZED_VALUE
override val value: T
get() {
val value = _value
if (value !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return value as T
}
val initializerValue = initializer
// if we see null in initializer here, it means that the value is already set by another thread
if (initializerValue != null) {
val newValue = initializerValue()
/* 값이 초기화 되지 않았을 경우, 새로운 값으로 초기화 */
if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
initializer = null
return newValue
}
}
@Suppress("UNCHECKED_CAST")
return _value as T
}
//...
}
None
초기화 관련 로직이 여러 번 수행될 수 있지만
맨 처음에 반환된 값 만이 초기화 값으로 쓰인다
예제코드)
import java.time.LocalDateTime
class HelloBot{
val greeting: String by lazy(LazyThreadSafetyMode.NONE) {
println("초기화 수행")
getHello()
};
fun sayHello() = println(greeting);
}
fun getHello() = "안녕하세요 현재시간은 " + LocalDateTime.now() + " 입니다."
fun main(){
val helloBot = HelloBot()
for(i in 1 until 5) {
Thread{
helloBot.sayHello();
}.start();
}
}
결과)
초기화 로직이 여러 번 수행되고,
초기화 된 값도 다른 것을 볼 수 있다
초기화 수행
초기화 수행
초기화 수행
초기화 수행
안녕하세요 현재시간은 2023-10-30T22:02:15.458923 입니다.
안녕하세요 현재시간은 2023-10-30T22:02:15.458907 입니다.
안녕하세요 현재시간은 2023-10-30T22:02:15.458929 입니다.
안녕하세요 현재시간은 2023-10-30T22:02:15.458909 입니다.
Lazy Init과 관련된 소스코드를 자세히 살펴보면 아래와 같다
internal class UnsafeLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
private var _value: Any? = UNINITIALIZED_VALUE
override val value: T
get() {
if (_value === UNINITIALIZED_VALUE) {
_value = initializer!!()
initializer = null
}
/* 무조건 매 순간에 주어진 값을 초기화 */
@Suppress("UNCHECKED_CAST")
return _value as T
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
'Programming Language > Kotlin' 카테고리의 다른 글
Late Init (1) | 2023.10.31 |
---|---|
Lazy Init - 기본 (0) | 2023.10.28 |
변성 (0) | 2023.10.21 |
댓글