본문 바로가기
Programming Language/Kotlin

Lazy Init - Synchronization 옵션

by YellowCow 2023. 10. 30.

배경

코틀린의 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

댓글