How do Java 21 virtual threads compare to Kotlin coroutines?
When coding in Kotlin, is it better to prefer one over the other?
This video: Java 21 new feature: Virtual Threads #RoadTo21 seems to frown upon using virtual threads for non-IO or non-blocking tasks.
I create coroutines right and left even for CPU-intensive tasks in my Kotlin code. Is this not alright anymore?
Is this not alright anymore? It is alright, Java virtual threads won't replace Kotlin coroutines.
There is a presentation by Roman Elizarov that covers the difference.
Short Recap from presentation
Virtual Threads (Project Loom) are good for
Kotlin Coroutines are good for
And, as mentioned by @Slaw in the comment, coroutines can be executed on virtual threads too. Also coroutines can be used with Kotlin/JS and Kotlin/Native.
From this video, we can say that Loom is great. But it's not a replacement for Kotlin coroutines. Coroutines are still the recommended thing to use when dealing with concurrent processes in Kotlin. To compare them,
To wrap up, we can say that the best thing Loom has to propose is the virtual threads which can improve performance, while Kotlin coroutines have more to offer. Why not use both of the features together?
Well, there is a concept introduced by Moskala in his "Kotlin Coroutines: Deep Dive" book which is interesting. We can use Loom directly in Kotlin Coroutines code and have better performance while keeping structured concurrency and all the cool stuff we get in Coroutines. To do that, we use virtual threads to replace Dispatchers.IO
. Below is an example of code that uses a virtual thread.
val LoomDispatcher = Executors
.newVirtualThreadPerTaskExecutor()
.asCoroutineDispatcher()
val Dispatchers.Loom: CoroutineDispatcher
get() = LoomDispatcher
suspend fun main() = measureTimeMillis {
coroutineScope {
repeat(100_000) {
launch(Dispatchers.Loom) {
Thread.sleep(1000)
}
}
}
}.let(::println)
Because Dispatchers.IO
has just 64 threads, the code above will take over 26 minutes, but we can make use of limitedParallelism
to increase the number of threads and get the chance to execute the code quickly. The code using Dispatchers.IO
is as follows:
suspend fun main() = measureTimeMillis {
val dispatcher = Dispatchers.IO
.limitedParallelism(100_000)
coroutineScope {
repeat(100_000) {
launch(dispatcher) {
Thread.sleep(1000)
}
}
}
}.let(::println)
I didn't personally run the code with virtual threads but it's said in the book that it took a bit more than two seconds to execute (This is amazing knowing that we block each of the 100,000 threads for 1 second), but the second code took around 30s to finish executing.
I would say the best way to make the code performant and use some cool stuff that Coroutines offers, we can use Loom as a substitution for Dispatchers.IO
but keep using Coroutines, though there are no any kinds of recommendations in the documentation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With