Search code examples
kotlinkotlin-coroutines

Are Java thread-safe collections safe with Kotlin coroutines?


I was working with a colleague today who was developing an application which utilized Kotlin Coroutines to parallelize operations which add items to a queue. The codes uses a Mutex (from the kotlin.coroutines.sync package). It looked like this sample code:

val queue = ArrayDeque<Deferred<ConnectorMessage>>()
val queueMutationMutex = Mutex()
myCoroutineScope.launch(Dispatchers.IO){
   repeat(1000){
      launch{
         queueMutationMutex.withLock{
               queue.add(foo)
         }
      }
   }
}

I said to him, why not just use a thread-safe queue, like one recommended here? Then you wouldn't need a Mutex. He replied by saying that because Java's concurrent data structures assume that concurrent operations run in separate threads, but Kotlin's coroutines may possibly run in the same thread, that the promises put forth by the JVM regarding thread safety no longer apply.

Is this true?


Solution

  • It is safe to use thread-safe collections from multiple coroutines. Still, it could be better to synchronize using utilities for coroutines.

    Explanation

    Threads are carriers of coroutines. At a given time a single thread can only ever execute a single coroutine. For this reason, if we limit a critical section to a single thread, this inevitably means we limited it to a single coroutine as well. This is assuming we don't suspend inside the critical section (which we don't do in Java code).

    However, we need to remember thread blocking is disallowed/discouraged in coroutine code. For this reason we should generally avoid using Java synchronization utilities with coroutines. They wait by blocking the thread, making coroutines unresponsive. In the case of synchronized collections, the waiting time could be so short and infrequent that blocking is not a problem, but it really depends on a use scenario.

    Another case are methods / data structures that achieve thread-safety by other means than blocking (e.g. ConcurrentLinkedQueue). If they can handle multiple threads running through them, then they will properly handle multiple coroutines as well. From their perspective there are no coroutines - only threads.

    Finally, if we control the data structure itself, then we don't really need to use neither e.g. BlockingQueue nor ArrayDeque + mutex. Equivalent of BlockingQueue in coroutine world is Channel, so we can use it and not worry about multiple coroutines.