The lazy function declaration takes a function that returns a generic T, and the return value is a lazy object that also contains a generic T
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
Copy the code
So what is Lazy, and let’s see what it is
public interface Lazy<out T> {
/** * Gets the lazily initialized value of the current Lazy instance. * Once the value was initialized it must not change during the rest of lifetime of this Lazy instance. */
public val value: T
/** * Returns `true` if a value for this Lazy instance has been already initialized, and `false` otherwise. * Once this function has returned `true` it stays `true` for the rest of lifetime of this Lazy instance. */
public fun isInitialized(a): Boolean
}
Copy the code
As the comment makes clear, Lazy is an interface that includes a value that we return in by Lazy {} and a method isInitialized() to determine if the current value has been initialized.
Let’s go back to the lazy method, which simply returns a SynchronizedLazyImpl. Let’s examine the SynchronizedLazyImpl
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 vallock = 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 {
valtypedValue = initializer!! () _value = typedValue initializer =null
typedValue
}
}
}
override fun isInitialized(a): Boolean= _value ! == UNINITIALIZED_VALUE }Copy the code
SynchronizedLazyImpl implements Lazy. The SynchronizedLazyImpl accepts two arguments: a closure passed by Lazy {} and an object of any type, null by default. The class then uses a variable initializer to hold the closure passed in the constructor and gives _value an initial value, UNINITIALIZED_VALUE, which is simply an empty object with no implementation. The lock object is then assigned, and when the lock passed in the constructor is null, the value of the lock is assigned to the current object. So that’s how SynchronizedLazyImpl implements lazy loading.
SynchronizedLazyImpl overwrites the value attribute of Lazy and modifies its get() method
val _v1 = _value
if(_v1 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")
return _v1 as T
}
Copy the code
UNINITIALIZED_VALUE: UNINITIALIZED_VALUE: UNINITIALIZED_VALUE: UNINITIALIZED_VALUE: UNINITIALIZED_VALUE: UNINITIALIZED_VALUE: UNINITIALIZED_VALUE Otherwise, execute the following code
return synchronized(lock) {
val _v2 = _value
if(_v2 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
valtypedValue = initializer!! () _value = typedValue initializer =null
typedValue
}
}
Copy the code
As you can see in this code logic, the return value is locked, and then the value of _value is compared to UNINITIALIZED_VALUE. This is done to ensure that only one thread initializes the value in a multithreaded environment, consistent with the DCL singleton idea. Else statements do the actual initialization logic, which executes the closure and returns the value.
That’s how lazy{} implements lazy loading.
In fact, in addition to the default implementation, Kotlin provides several other implementations, which can be divided into three, depending on whether they are thread-safe, in the form of an enumerated class.
public enum class LazyThreadSafetyMode {
/** * Locks are used to ensure that only a single thread can initialize the [Lazy] instance. */
SYNCHRONIZED,
/** * Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value, * but only the first returned value will be used as the value of [Lazy] instance. */
PUBLICATION,
/** * No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined. * * This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread. */
NONE,
}
Copy the code
The comments make it clear that SYNCHRONIZED is used by default in by lazy{} to keep threads safe, whereas PUBLICATION is an initialization method that can be called multiple times but whose value is only the value returned the first time. NONE means not locked. We can build different lazy objects by passing in enumerated values when we call lazy.
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)
}
Copy the code
Let’s take a look at the SafePublicationLazyImpl and UnsafeLazyImpl implementations, which are otherwise consistent with SynchronizedLazyImpl, focusing on value’s get() method
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
}
Copy the code
Roughly the same logic as SynchronizedLazyImpl Only a little different place is at the time of set values is to use a valueUpdater.com pareAndSet (this, UNINITIALIZED_VALUE, newValue) to _value assignment, The valueUpdater is an atomic property updater, with the following declarations for interested readers.
private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
SafePublicationLazyImpl::class.java,
Any::class.java,
"_value"
)
Copy the code
In this way only the first return value can be assigned to _value.
Then we look at UnsafeLazyImpl, which is a simpler implementation, just an unlocked version of SafePublicationLazyImpl
override val value: T
get() {
if(_value === UNINITIALIZED_VALUE) { _value = initializer!! () initializer =null
}
@Suppress("UNCHECKED_CAST")
return _value as T
}
Copy the code
That completes most of the analysis of Lazy in Kotlin