Concurrency and Multithreading in Kotlin: Coroutines, Threads, and Practical Examples

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 Theory to Practice

Modern applications require fast and responsive performance. Whether it's loading data from the network, processing large arrays, or working with the user interface — concurrency and multithreading are essential. Kotlin, being a modern language for JVM, Android, and multiplatform projects, offers flexible tools for solving these problems. In this article, we will break down key approaches: from classic threads to lightweight coroutines, and show how to write efficient code.



1. Threads in Kotlin: The Classic Approach

Threads are a basic mechanism for parallel execution inherited from Java. In Kotlin, you can create threads the same way as in Java, but with a more concise syntax.

A simple example of starting a thread:

fun main() {    thread {        println("Thread started: ${Thread.currentThread().name}")        Thread.sleep(1000)        println("Thread finished")    }    println("Main thread: ${Thread.currentThread().name}")    Thread.sleep(2000)}

However, threads have disadvantages: they are heavyweight (each thread consumes about 1 MB of memory), their number is limited, and context switching is expensive. For modern applications with thousands of parallel tasks, threads are not the best choice. This is where coroutines come to the rescue.



2. Coroutines: A Lightweight Alternative

Coroutines are the main feature of Kotlin for concurrency. They work on top of threads but use fewer resources. One thread can execute thousands of coroutines, suspending and resuming them without blocking.

To get started, add the dependency to build.gradle.kts:

dependencies {    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")}

Example of a simple coroutine:

import kotlinx.coroutines.*

fun main() = runBlocking { launch { delay(1000L) // non-blocking delay println("World from coroutine!") } println("Hello from main coroutine") delay(2000L)}

Here, launch starts a new coroutine, and delay suspends it without blocking the thread. runBlocking is used to block the main thread until all coroutines complete (typically used only in main and tests).



2.1. Suspend Functions

The suspend keyword allows you to pause the execution of a function without blocking the thread. This is the foundation of asynchronous programming in Kotlin.

suspend fun fetchUser(): String {    delay(500L) // simulating a network request    return "User123"}

fun main() = runBlocking { val user = fetchUser() println("User received: $user")}


2.2. Async and Deferred for Parallel Tasks

If you need to execute several tasks in parallel and wait for their results, use async and Deferred:

fun main() = runBlocking {    val time = measureTimeMillis {        val user = async { fetchUser() }        val profile = async { fetchProfile() }        println("Results: ${user.await()}, ${profile.await()}")    }    println("Execution time: $time ms")}

suspend fun fetchUser(): String { delay(500) return "User"}

suspend fun fetchProfile(): String { delay(500) return "Profile"}

Both tasks execute in parallel, reducing the total time.



3. Coroutine Dispatchers

Dispatchers determine which threads the coroutines will run on. The main types are:

  • Dispatchers.Default — for CPU-intensive tasks (default).
  • Dispatchers.IO — for I/O operations (network, files).
  • Dispatchers.Main — for the UI thread (Android, JavaFX).

Blogs

Book Recommendations