Provide LazyThreadSafetyMode.PUBLICATION: concurrent non-blocking initializations, single publication.
This commit is contained in:
@@ -43,6 +43,12 @@ public enum class LazyThreadSafetyMode {
|
|||||||
*/
|
*/
|
||||||
SYNCHRONIZED,
|
SYNCHRONIZED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value,
|
||||||
|
* but only first returned value will be used as the value of [Lazy] instance.
|
||||||
|
*/
|
||||||
|
PUBLICATION,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No locks are used to synchronize the access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
|
* No locks are used to synchronize the access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package kotlin
|
package kotlin
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
|
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
|
||||||
@@ -28,6 +30,7 @@ public fun lazy<T>(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initial
|
|||||||
public fun lazy<T>(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
|
public fun lazy<T>(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
|
||||||
when (mode) {
|
when (mode) {
|
||||||
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
|
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
|
||||||
|
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
|
||||||
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
|
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,3 +46,34 @@ public fun lazy<T>(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
|
|||||||
* Also this behavior can be changed in the future.
|
* Also this behavior can be changed in the future.
|
||||||
*/
|
*/
|
||||||
public fun lazy<T>(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)
|
public fun lazy<T>(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)
|
||||||
|
|
||||||
|
|
||||||
|
private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T>(), Serializable {
|
||||||
|
private var initializer: (() -> T)? = initializer
|
||||||
|
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
|
||||||
|
private val final: Any = UNINITIALIZED_VALUE
|
||||||
|
|
||||||
|
override val value: T
|
||||||
|
get() {
|
||||||
|
if (_value === UNINITIALIZED_VALUE) {
|
||||||
|
val newValue = initializer!!()
|
||||||
|
if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
|
||||||
|
initializer == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
|
||||||
|
SafePublicationLazyImpl::class.java,
|
||||||
|
Any::class.java,
|
||||||
|
"_value")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,78 @@ package test.utils
|
|||||||
import kotlin.*
|
import kotlin.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import kotlin.concurrent.thread
|
||||||
import org.junit.Test as test
|
import org.junit.Test as test
|
||||||
|
|
||||||
class LazyJVMTest {
|
class LazyJVMTest {
|
||||||
|
|
||||||
|
@test fun synchronizedLazy() {
|
||||||
|
val counter = AtomicInteger(0)
|
||||||
|
val lazy = lazy {
|
||||||
|
val value = counter.incrementAndGet()
|
||||||
|
Thread.sleep(100)
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
val accessThreads = listOf(lazy, lazy).map { thread { it.value } }
|
||||||
|
accessThreads.forEach { it.join() }
|
||||||
|
|
||||||
|
assertEquals(1, counter.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
@test fun externallySynchronizedLazy() {
|
||||||
|
val counter = AtomicInteger(0)
|
||||||
|
var initialized: Boolean = false
|
||||||
|
val runs = ConcurrentHashMap<Int, Boolean>()
|
||||||
|
val lock = Any()
|
||||||
|
|
||||||
|
val initializer = {
|
||||||
|
val value = counter.incrementAndGet()
|
||||||
|
runs += (value to initialized)
|
||||||
|
Thread.sleep(100)
|
||||||
|
initialized = true
|
||||||
|
value
|
||||||
|
}
|
||||||
|
val lazy1 = lazy(lock, initializer)
|
||||||
|
val lazy2 = lazy(lock, initializer)
|
||||||
|
|
||||||
|
val accessThreads = listOf(lazy1, lazy2).map { thread { it.value } }
|
||||||
|
accessThreads.forEach { it.join() }
|
||||||
|
|
||||||
|
assertEquals(2, counter.get())
|
||||||
|
for ((counter, initialized) in runs) {
|
||||||
|
assertEquals(initialized, counter == 2, "Expected uninitialized on first, initialized on second call: initialized=$initialized, counter=$counter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@test fun publishOnceLazy() {
|
||||||
|
val counter = AtomicInteger(0)
|
||||||
|
var initialized: Boolean = false
|
||||||
|
val runs = ConcurrentHashMap<Int, Boolean>()
|
||||||
|
|
||||||
|
val initializer = {
|
||||||
|
val value = counter.incrementAndGet()
|
||||||
|
runs += (value to initialized)
|
||||||
|
Thread.sleep((3 - value) * 100L)
|
||||||
|
initialized = true
|
||||||
|
value
|
||||||
|
}
|
||||||
|
val lazy = lazy(LazyThreadSafetyMode.PUBLICATION, initializer)
|
||||||
|
|
||||||
|
val accessThreads = listOf(lazy, lazy).map { thread { it.value } }
|
||||||
|
accessThreads.forEach { it.join() }
|
||||||
|
|
||||||
|
assertEquals(2, counter.get())
|
||||||
|
assertEquals(2, lazy.value)
|
||||||
|
for ((counter, initialized) in runs) {
|
||||||
|
assertFalse(initialized, "Expected uninitialized on first and second run")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@test fun lazyInitializationForcedOnSerialization() {
|
@test fun lazyInitializationForcedOnSerialization() {
|
||||||
for(mode in listOf(LazyThreadSafetyMode.SYNCHRONIZED, LazyThreadSafetyMode.NONE)) {
|
for(mode in listOf(LazyThreadSafetyMode.SYNCHRONIZED, LazyThreadSafetyMode.PUBLICATION, LazyThreadSafetyMode.NONE)) {
|
||||||
val lazy = lazy(mode) { "initialized" }
|
val lazy = lazy(mode) { "initialized" }
|
||||||
assertFalse(lazy.isInitialized())
|
assertFalse(lazy.isInitialized())
|
||||||
val lazy2 = serializeAndDeserialize(lazy)
|
val lazy2 = serializeAndDeserialize(lazy)
|
||||||
|
|||||||
Reference in New Issue
Block a user