Kotlin by lazy under the hood

huiung
3 min readAug 30, 2022

--

Photo by Alison Ivansek on Unsplash

In Kotlin, there are keywords(lateinit, by lazy) to initialize variables lazily.

Take the code below as an example.

val a : String by lazy { .... }

It is initialized by running lazy {…} block only once when accessing it for the first time. So it has the advantage of late initialization when only needed it.

Let’s see how lazy block can do this.

We can find a definition of a specific Class, Function, or Keyword.. in Android Studio using shortcuts (Ctrl + B, Command + B, … )

If we find a definition of “by”, we can see below code in Lazy.Kt . It defines the Lazy<T>.getValue operator which is Kotlin delegation operator. It’s “by” keyword’s role. It returns “value” which is the property of the Lazy Class.

Lazy.kt

@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

Then, we can guess that lazy { … } block return Lazy<T> object. The code below is the definition of lazy block

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

It calls the SynchronizedLazyImpl function inside. It is the main point of by lazy { … } block.

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
}
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
}
}
}

It’s a bit long code. If you’re familiar with the pattern, you’ll notice, but this is the double checked locking pattern that is used to implement Sigleton Pattern.

This pattern is a method that can perform minimal blocking while ensuring synchronization(Thread-safe) even in a multi-threaded environment by checking twice whether to initialize as shown above.

Let’s go through the code.

First, “_value” is declared Volatile keyword to ensure memory visibility

@Volatile private var _value: Any? = UNINITIALIZED_VALUE

variable “value” override get() method. It check whether to initialize If not initialized yet, enter the synchronized block. Otherwise, return _v1. So It can minimize blocking.

override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
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
}
}
}

What might be questionable is the existence of a variable “typedValue”. Why is it like “val typedValue = initializer!!()” not “_value = initializer!!()”

This is to avoid the reordering problem which is part of the compiler’s optimization.

If the code above is “_value = initializer!!()”, let’s take a simple example of what kind of problem occurs.

That code goes through 3 steps in assembly.

  1. Allocate memory to store the initializer object
  2. Call the initializer’s constructor
  3. Pass the reference to object to _value

If we proceed in the above order, there is no problem, but the order may be changed during the compiler’s optimization process.

Then, If the order has changed 2 and 3? do not create an object yet, but _value has reference to the object. At this time, If we access it from another thread, It returns the object’s reference that isn’t created yet. It can cause problems. Therefore, after declaring one “typedValue” variable and completing creation, it is put into value.

That’s it! To sum up, by lazy { … } is that create a Singleton object when accessing it the first time. And Singleton Pattern is implemented double-checked locking pattern.

val a : String by lazy { .... } //under the hood
val a = lazy(initializer:() -> T).getValue( )

--

--