diff --git a/libraries/stdlib/src/kotlin/concurrent/Thread.kt b/libraries/stdlib/src/kotlin/concurrent/Thread.kt index e8c2df8b058..247dafcbe40 100644 --- a/libraries/stdlib/src/kotlin/concurrent/Thread.kt +++ b/libraries/stdlib/src/kotlin/concurrent/Thread.kt @@ -80,6 +80,22 @@ public fun thread(start: Boolean = true, daemon: Boolean = false, contextClassLo return thread } +/** + * Gets the value in the current thread's copy of this + * thread-local variable or replaces the value with the result of calling + * [default] function in case if that value was `null`. + * + * If the variable has no value for the current thread, + * it is first initialized to the value returned + * by an invocation of the [ThreadLocal.initialValue] method. + * Then if it is still `null`, the provided [default] function is called and its result + * is stored for the current thread and then returned. + */ +public inline fun ThreadLocal.getOrSet(default: () -> T): T { + // TODO: replace let with apply or touch + return get() ?: default().let { set(it); it } +} + /** * Allows you to use the executor as a function to * execute the given block on the [Executor]. diff --git a/libraries/stdlib/test/concurrent/ThreadTest.kt b/libraries/stdlib/test/concurrent/ThreadTest.kt index e779cd3f93d..39ee65835e5 100644 --- a/libraries/stdlib/test/concurrent/ThreadTest.kt +++ b/libraries/stdlib/test/concurrent/ThreadTest.kt @@ -28,4 +28,24 @@ class ThreadTest { assertEquals("Hello", future.get(2, SECONDS)) } + test fun threadLocalGetOrSet() { + val v = ThreadLocal() + + assertEquals("v1", v.getOrSet { "v1" }) + assertEquals("v1", v.get()) + assertEquals("v1", v.getOrSet { "v2" }) + + v.set(null) + assertEquals("v2", v.getOrSet { "v2" }) + + v.set("v3") + assertEquals("v3", v.getOrSet { "v2" }) + + + val w = object: ThreadLocal() { + override fun initialValue() = "default" + } + + assertEquals("default", w.getOrSet { "v1" }) + } } \ No newline at end of file