From the library I use I get ConcurrentMap. I want to obtain it's keys as Set.
How to do it in thread-safe way (since Kotlin collections API is thread-unsafe)?
Guarantees expected upon implementations of ConcurrentMap's implementations according to the docs:
A Map providing thread safety and atomicity guarantees.
To maintain the specified guarantees, default implementations of methods including putIfAbsent(K, V) inherited from Map must be overridden by implementations of this interface. Similarly, implementations of the collections returned by methods Map.keySet(), Map.values(), and Map.entrySet() must override methods such as removeIf when necessary to preserve atomicity guarantees.
Memory consistency effects: As with other concurrent collections, actions in a thread prior to placing an object into a ConcurrentMap as a key or value happen-before actions subsequent to the access or removal of that object from the ConcurrentMap in another thread.
This interface is a member of the Java Collections Framework.
An implementation is ConcurrentHashMap:
A hash table supporting full concurrency of retrievals and high expected concurrency for updates. This class obeys the same functional specification as Hashtable, and includes versions of methods corresponding to each method of Hashtable. However, even though all operations are thread-safe, retrieval operations do not entail locking, and there is not any support for locking the entire table in a way that prevents all access. This class is fully interoperable with Hashtable in programs that rely on its thread safety but not on its synchronization details.
ConcurrentHashMaps support a set of sequential and parallel bulk operations that, unlike most Stream methods, are designed to be safely, and often sensibly, applied even with maps that are being concurrently updated by other threads; for example, when computing a snapshot summary of the values in a shared registry. There are three kinds of operation, each with four forms, accepting functions with keys, values, entries, and (key, value) pairs as arguments and/or return values. Because the elements of a ConcurrentHashMap are not ordered in any particular way, and may be processed in different orders in different parallel executions, the correctness of supplied functions should not depend on any ordering, or on any other objects or values that may transiently change while computation is in progress; and except for forEach actions, should ideally be side-effect-free. Bulk operations on Map.Entry objects do not support method setValue.
Another implementation is ConcurrentSkipListMap:
This class implements a concurrent variant of SkipLists providing expected average log(n) time cost for the containsKey, get, put and remove operations and their variants. Insertion, removal, update, and access operations safely execute concurrently by multiple threads.
I suggest that you should try the scenarios you are worried about with one (or both) of these implementations, see whether they fulfill your expectation and if so, then you're done. If not, then detect which methods do not behave in the way you expect and create a subclass where you fix the issues and use that instead.
Just access it normally:
val keys: MutableSet<YourKeyType> = getConcurrentMap().keys
You can even remove entries from the map in a thread-safe way using the key set:
keys.remove(someKey)
Just keep in mind that the content of keys can change anytime (because it is mutable), but you can be sure that you won't run into a ConcurrentAccessException under any circumstances.
Explanation:
Kotlin follows another paradigm when it comes to shared mutable state than Java.
Java has had a clunky concurrency handling for the most time, so the easiest way for the user to obtain thread-safety is when the data structures themselves have that built in, like the ConcurrentMap implementations.
Kotlin on the other hand refrains from providing built-in concurrency support for its data structures. The user is supposed to manage the thread safety, which fortunately is pretty easy with coroutines. That also has the benefit that you can manage complex state where multiple data structures are involved the same way as simple state which is more efficient and less error prone to get it wrong.
The reason why using a ConcurrentMap implementation in Kotlin simply works is that the Java implementation already provides the thread safety. Whatever you do with it in Kotlin, it doesn't lose that characteristic. Using the key set to remove an item from the map is thread-safe as well because the ConcurrentMap's implementation made sure to provide a thread-safe set of keys that is linked to the map. So any modification of that set also modifies the map - the thread-safety is built in.
Channel which is essentially a coroutine-safe queue.
ConcurrentMapmakes various guarantees. Even if you use it via theMapinterface, those guarantees still hold.getOrPut. They were added by Kotlin and they aren't entirely thread-safe even forConcurrentMap. Otherwise, it doesn't matter if we use Java or Kotlin.ConcurrentHashMap's internalputValmethod for its thread-safety.ConcurrentMap. If we store it as justMutableMap, we will use implementation which is not thread-safe. I suspect most people use the base map type.MutableMap. So the behavior wouldn't be that unexpected.