Introduction: Why Do You Need Task Queues in Kotlin?
\nModern applications increasingly require parallel data processing, background operations, and asynchronous calls. Without proper task management, your code can face performance issues, memory leaks, or UI freezes. Task queues in Kotlin are not just an abstraction, but a powerful tool that helps organize work execution in the right order, with control over the number of threads and priorities.
\nIn this article, we will explore practical approaches to implementing task queues in Kotlin: from the classic ThreadPoolExecutor to coroutines with their flexible dispatchers. You will see how to apply these techniques in real projects and get ready-to-use code examples.
1. Classic Approach: ThreadPoolExecutor and BlockingQueue
\nThe standard Java library (and Kotlin, as a JVM language) provides the powerful ThreadPoolExecutor class. It manages a pool of threads and uses an internal blocking queue to store pending tasks.
Creating a Queue with a Fixed Number of Threads
\nThe simplest case is a pool with a fixed number of threads. All tasks that cannot be executed immediately are placed in a LinkedBlockingQueue.
import java.util.concurrent.Executors\nimport java.util.concurrent.TimeUnit\n\nfun main() {\n val executor = Executors.newFixedThreadPool(3)\n\n for (i in 1..10) {\n executor.submit {\n println("Task $i is executing in thread ${Thread.currentThread().name}")\n Thread.sleep(1000)\n }\n }\n\n executor.shutdown()\n executor.awaitTermination(1, TimeUnit.MINUTES)\n println("All tasks completed")\n}\n\nThis code demonstrates a basic queue: tasks are submitted to the pool, and if all threads are busy, they wait in the queue.
\n\nPriority Queue: PriorityBlockingQueue
\nSometimes you need to execute urgent tasks before others. For this, we use PriorityBlockingQueue with a custom comparator.
import java.util.concurrent.PriorityBlockingQueue\nimport java.util.concurrent.ThreadPoolExecutor\nimport java.util.concurrent.TimeUnit\n\ndata class PriorityTask(val priority: Int, val name: String) : Comparable<PriorityTask> {\n override fun compareTo(other: PriorityTask): Int = other.priority - this.priority\n}\n\nfun main() {\n val queue = PriorityBlockingQueue<Runnable>(10) { r1, r2 ->\n val t1 = (r1 as PriorityTaskWrapper).task\n val t2 = (r2 as PriorityTaskWrapper).task\n t1.compareTo(t2)\n }\n\n val executor = ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, queue)\n\n class PriorityTaskWrapper(val task: PriorityTask, val action: () -> Unit) : Runnable {\n override fun run() = action()\n }\n\n for (i in 1..5) {\n val priority = if (i % 2 == 0) 10 else 1\n executor.submit(PriorityTaskWrapper(PriorityTask(priority, "Task $i")) {\n println("Executing: ${PriorityTask(priority, "Task $i")}")\n Thread.sleep(500)\n })\n }\n\n executor.shutdown()\n executor.awaitTermination(1, TimeUnit.MINUTES)\n}\n\nHere, tasks with priority = 10 will be executed before those with priority = 1, even if they are added later.
2. Task Queues Using Coroutines (kotlinx.coroutines)
\nModern Kotlin offers coroutines — lightweight threads that allow building asynchronous queues without direct thread management. The main tools are Channel and Dispatchers.
Simple Queue via Channel
\nChannel is an analog of a blocking queue, but for coroutines. It allows safely passing tasks between coroutines.
import kotlinx.coroutines.*\nimport kotlinx.coroutines.channels.Channel\n\nsuspend fun processTas