diff --git a/libraries/stdlib/src/kotlin/collections/MapsJVM.kt b/libraries/stdlib/src/kotlin/collections/MapsJVM.kt index 1fef309a9d2..19ea6317aa4 100644 --- a/libraries/stdlib/src/kotlin/collections/MapsJVM.kt +++ b/libraries/stdlib/src/kotlin/collections/MapsJVM.kt @@ -15,12 +15,28 @@ public fun MutableMap.set(key: K, value: V): V? = put(key, value) /** * getOrPut is not supported on [ConcurrentMap] since it cannot be implemented correctly in terms of concurrency. - * Use [ConcurrentMap.putIfAbsent] instead, or cast this to a [MutableMap] if you want to sacrifice the concurrent-safety. + * Use [concurrentGetOrPut] instead, or cast this to a [MutableMap] if you want to sacrifice the concurrent-safety. */ -deprecated("Use putIfAbsent instead or cast this map to MutableMap.") +deprecated("Use concurrentGetOrPut instead or cast this map to MutableMap.") public inline fun ConcurrentMap.getOrPut(key: K, defaultValue: () -> V): Nothing = throw UnsupportedOperationException("getOrPut is not supported on ConcurrentMap.") +/** + * Concurrent getOrPut, that is safe for concurrent maps. + * + * Returns the value for the given [key]. If the key is not found in the map, calls the [defaultValue] function, + * puts its result into the map under the given key and returns it. + * + * This method guarantees not to put the value into the map if the key is already there, + * but the [defaultValue] function may be invoked even if the key is already in the map. + */ +public inline fun ConcurrentMap.concurrentGetOrPut(key: K, defaultValue: () -> V): V { + // Do not use computeIfAbsent on JVM8 as it would change locking behavior + return this.get(key) ?: + defaultValue().let { default -> this.putIfAbsent(key, default) ?: default } + +} + /** * Converts this [Map] to a [SortedMap] so iteration order will be in key order. * diff --git a/libraries/stdlib/test/collections/MapJVMTest.kt b/libraries/stdlib/test/collections/MapJVMTest.kt index a02d080f149..eef2cd9626b 100644 --- a/libraries/stdlib/test/collections/MapJVMTest.kt +++ b/libraries/stdlib/test/collections/MapJVMTest.kt @@ -1,6 +1,7 @@ package test.collections import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap import kotlin.test.* import org.junit.Test as test @@ -45,6 +46,9 @@ class MapJVMTest { fails { map.getOrPut("x") { 1 } } + expect(1) { + map.concurrentGetOrPut("x") { 1 } + } expect(1) { (map as MutableMap).getOrPut("x") { 1 } }