Concurrency and Multithreading in Kotlin: From A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Concurrency and Multithreading in Kotlin: From A to Z



Modern applications increasingly require executing multiple tasks simultaneously. In Kotlin, as in other JVM languages, multithreading is traditionally implemented through the Thread class. However, the language offers a much more powerful and elegant tool — coroutines. In this article, we will cover all aspects of concurrency and multithreading in Kotlin: from basic concepts to advanced techniques.



1. Basics of Multithreading: Thread and Runnable



Let's start with the classic approach. In Kotlin, as in Java, you can create a thread directly:



fun main() {    val thread = Thread {        for (i in 1..5) {            println("Thread: $i")            Thread.sleep(500)        }    }    thread.start()    println("Main thread finished")}


However, manual thread management has drawbacks:

  • High overhead — creating and destroying threads is expensive.
  • Complex synchronization — it's easy to introduce a race condition.
  • Limited number of threads — the system can only create a limited number of threads.


To synchronize access to shared data, the synchronized keyword or ReentrantLock locks are used:



class Counter {    private var count = 0    @Synchronized    fun increment() {        count++    }}


2. Coroutines — A Modern Approach to Concurrency



Coroutines are lightweight threads that solve the problems of classic multithreading. They are not tied to system threads and can execute on a single thread, switching between tasks.



2.1. First Coroutine



To work with coroutines, you need to add the dependency org.jetbrains.kotlinx:kotlinx-coroutines-core. Example of a simple coroutine:



import kotlinx.coroutines.*

fun main() = runBlocking { launch { delay(1000L) println("World!") } println("Hello,")}


Here, runBlocking is a bridge between regular code and coroutines. launch starts a new coroutine, and delay is an analog of Thread.sleep(), but it does not block the thread.



2.2. Suspend Functions



The suspend keyword allows pausing the execution of a function without blocking the thread:



suspend fun fetchData(): String {    delay(2000L) // simulating a network request    return "Data received"}

fun main() = runBlocking { val result = async { fetchData() } println("Waiting...") println(result.await())}


2.3. Structured Concurrency



Kotlin supports structured concurrency — all child coroutines automatically complete before the parent coroutine completes:



fun main() = runBlocking {    coroutineScope {        launch {            delay(1000L)            println("Task 1")        }        launch {            delay(500L)            println("Task 2")        }    }    println("All tasks completed")}


3. Advanced Techniques: Channel, Flow, and Dispatchers



3.1. Channels



Channels allow data exchange between coroutines. They are an analog of a queue:



fun main() = runBlocking {    val channel = Channel<Int>()        launch {        for (i in 1..5) {            channel.send(i * i)        }        channel.close()    }        for (value in channel) {        println(value)    }}


3.2. Flow — Cold Data Streams



Flow is a reactive stream that emits values on demand:



fun simpleFlow(): Flow<Int> = flow {    for (i in 1..3) {        delay(                

Blogs

Book Recommendations