Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

优化/修复 ConcurrentMutableMap 在 Js、WasmJs 下会出现 ConcurrentModificationException 的问题,并为 MutableMap 增加一个扩展 API removeValue(key, value) #781

Merged
merged 1 commit into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,22 @@ public expect inline fun <K, V> MutableMap<K, V>.computeValueIfPresent(
): V?

/**
* 如果平台支持,则得到一个可以并发操作的 [MutableMap],否则通过 [mutableMapOf] 得到一个普通的 [MutableMap]。
* 根据 [key] 删除指定的目标 [target]。
*/
public expect inline fun <K, V> MutableMap<K, V>.removeValue(key: K, crossinline target: () -> V): Boolean

/**
* 如果平台支持,则得到一个可以并发操作的 [MutableMap]。
*
* 根据不同的平台实现,得到的 [MutableMap] 允许在迭代过程中
* (例如使用 [MutableMap.keys]、[MutableMap.values]、[MutableMap.entries])
* 对原 map 进行修改,而不会引发 [ConcurrentModificationException],
* 但正在迭代的迭代器不保证可以实时感知到已经发生的修改。换言之这种并发修改是弱一致性的。
*
* [concurrentMutableMap] 得到的结果可能是弱一致性的。
*/
public expect fun <K, V> concurrentMutableMap(): MutableMap<K, V>



@PublishedApi
internal inline fun <K, V> MutableMap<K, V>.internalMergeImpl(
key: K,
Expand Down Expand Up @@ -154,3 +162,21 @@ internal inline fun <K, V> MutableMap<K, V>.internalComputeIfPresentImpl(key: K,
return null
}


@PublishedApi
internal inline fun <K, V> MutableMap<K, V>.internalRemoveValueImpl(
key: K,
crossinline target: () -> V
): Boolean {
val targetValue = target()
val iter = iterator()
while (iter.hasNext()) {
val entry = iter.next()
if (entry.key == key && entry.value == targetValue) {
iter.remove()
return true
}
}

return false
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,24 @@ class ConcurrentMapTests {
assertContains(setOf(1, 2), n2.value)
assertFalse(iterator.hasNext())
}
}

@Test
fun mapConcurrentModifyTest() {
with(concurrentMutableMap<Int, Int>()) {
put(1, 1)
put(2, 2)
put(3, 3)
val iter = iterator()
assertTrue(iter.hasNext())
val n1 = iter.next()
assertEquals(1, n1.key)
assertEquals(1, n1.value)
assertTrue(removeValue(2) { 2 })
assertTrue(iter.hasNext())
// assert no error
iter.next()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,177 @@ public actual inline fun <K, V> MutableMap<K, V>.computeValueIfPresent(
): V? = internalComputeIfPresentImpl(key, mappingFunction)

/**
* 通过 [mutableMapOf] 得到一个普通的 [MutableMap]。
* JS 平台中不需要操心并发问题。
* 根据 [key] 删除指定的目标 [target]。
*/
public actual fun <K, V> concurrentMutableMap(): MutableMap<K, V> = mutableMapOf()
public actual inline fun <K, V> MutableMap<K, V>.removeValue(
key: K,
crossinline target: () -> V
): Boolean = internalRemoveValueImpl(key, target)

/**
* 通过 [mutableMapOf] 得到一个允许并发修改的 [MutableMap],
*/
public actual fun <K, V> concurrentMutableMap(): MutableMap<K, V> =
JsConcurrentModifyMutableMap(mutableMapOf()) // mutableMapOf()

private class JsConcurrentModifyMutableMap<K, V>(private val source: MutableMap<K, V>) : MutableMap<K, V> by source {
override val keys: MutableSet<K>
get() = MutableKeySet(source, source.keys.toSet())

private class MutableKeySet<K>(private val source: MutableMap<K, *>, private val view: Set<K>) : MutableSet<K>,
Set<K> by view {
override fun add(element: K): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<K>): Boolean = throw UnsupportedOperationException()
override fun clear() {
source.clear()
}

override fun remove(element: K): Boolean {
return source.remove(element) != null
}

@Suppress("ConvertArgumentToSet")
override fun removeAll(elements: Collection<K>): Boolean {
return source.keys.removeAll(elements)
}

@Suppress("ConvertArgumentToSet")
override fun retainAll(elements: Collection<K>): Boolean {
return source.keys.retainAll(elements)
}

override fun iterator(): MutableIterator<K> =
MutableMapIterator(view = view.iterator(), mapper = { it }, remover = source::remove)
}


override val values: MutableCollection<V>
get() = MutableValueCollection(source, source.values.toList())

private class MutableValueCollection<V>(private val source: MutableMap<*, V>, private val view: List<V>) :
MutableCollection<V>, Collection<V> by view {
override fun add(element: V): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<V>): Boolean = throw UnsupportedOperationException()

override fun clear() {
source.clear()
}

override fun remove(element: V): Boolean {
return source.values.remove(element)
}

@Suppress("ConvertArgumentToSet")
override fun removeAll(elements: Collection<V>): Boolean {
return source.values.removeAll(elements)
}

@Suppress("ConvertArgumentToSet")
override fun retainAll(elements: Collection<V>): Boolean {
return source.values.retainAll(elements)
}

override fun iterator(): MutableIterator<V> =
MutableMapIterator(view = view.iterator(), mapper = { it }, remover = source.values::remove)
}

override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() = MutableEntrySet(source, source.toMap().entries.toSet())

private class MutableEntrySet<K, V>(
private val source: MutableMap<K, V>,
private val view: Set<Map.Entry<K, V>>
) : MutableSet<MutableMap.MutableEntry<K, V>> {
override val size: Int
get() = view.size

override fun contains(element: MutableMap.MutableEntry<K, V>): Boolean =
source[element.key] == element.value

override fun containsAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean =
all { contains(it) }

override fun isEmpty(): Boolean = view.isEmpty()

override fun add(element: MutableMap.MutableEntry<K, V>): Boolean {
val (k, v) = element
if (source.containsKey(k)) return false
source[k] = v
return true
}

override fun addAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
var added = false
for (e in elements) {
if (add(e) && !added) {
added = true
}
}

return added
}

override fun clear() {
source.clear()
}

override fun remove(element: MutableMap.MutableEntry<K, V>): Boolean {
return source.remove(element.key) != null
}

override fun removeAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
var removed = false
for (e in elements) {
if (remove(e) && !removed) {
removed = true
}
}

return removed
}

override fun retainAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
return source.keys.retainAll(elements.mapTo(mutableSetOf()) { it.key })
}

override fun iterator(): MutableIterator<MutableMap.MutableEntry<K, V>> = MutableMapIterator(view.iterator(),
mapper = { (oldKey, oldValue) ->

object : MutableMap.MutableEntry<K, V> {
override val key: K = oldKey
override var value: V = oldValue

override fun setValue(newValue: V): V {
val oldValue1 = value
return source.put(key, newValue).also { value = newValue }
?: oldValue1
}
}
},
remover = { target ->
source.remove(target.key)
})
}

private class MutableMapIterator<T, R>(
private val view: Iterator<T>,
private val mapper: (T) -> R,
private val remover: (T) -> Unit
) : MutableIterator<R> {
@Suppress("ClassName")
private object NO_VALUE

private var currentValue: Any? = NO_VALUE
override fun hasNext(): Boolean = view.hasNext()
override fun next(): R = view.next().also { currentValue = it }.let(mapper)

@Suppress("UNCHECKED_CAST")
override fun remove() {
val value = currentValue
if (value is NO_VALUE) {
throw NoSuchElementException()
}
remover(value as T)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ public actual inline fun <K, V> MutableMap<K, V>.computeValueIfPresent(
crossinline mappingFunction: (K, V & Any) -> V?
): V? = computeIfPresent(key) { k, v -> mappingFunction(k, v) }

/**
* 根据 [key] 删除指定的目标 [target]。
*/
public actual inline fun <K, V> MutableMap<K, V>.removeValue(key: K, crossinline target: () -> V): Boolean =
remove(key, target())

/**
* 得到 [ConcurrentHashMap].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ public actual inline fun <K, V> MutableMap<K, V>.computeValueIfPresent(
return internalComputeIfPresentImpl(key, mappingFunction)
}


/**
* 根据 [key] 删除指定的目标 [target]。
*/
public actual inline fun <K, V> MutableMap<K, V>.removeValue(key: K, crossinline target: () -> V): Boolean {
if (this is MutableMapOperators) {
return removeValue(key, target())
}

return internalRemoveValueImpl(key, target)
}

/**
* 得到一个基于 [AtomicReference] 的 CopyOnWrite Map 实现。
* 此 Map 中的修改操作是弱一致性的。
Expand Down Expand Up @@ -138,6 +150,8 @@ internal interface MutableMapOperators<K, V> : MutableMap<K, V> {
key: K,
mappingFunction: (K, V & Any) -> V?
): V?

fun removeValue(key: K, target: V): Boolean
}

private class AtomicCopyOnWriteConcurrentMutableMap<K, V>(initMap: Map<K, V>) : MutableMap<K, V>,
Expand Down Expand Up @@ -289,4 +303,15 @@ private class AtomicCopyOnWriteConcurrentMutableMap<K, V>(initMap: Map<K, V>) :
return result
}

override fun removeValue(key: K, target: V): Boolean {
var result = false
compareAndSetMap { oldMap ->
oldMap.toMutableMap().apply {
result = internalRemoveValueImpl(key) { target }
}
}

return result
}
}

Loading
Loading