AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

Coroutines in Kotlin vs Java Threads

You can find many articles that talk about Coroutines in English in Kotlin, but sometimes it is difficult to understand the real differences with threads. That’s why in this article “Coroutines in Kotlin vs Java Threads,” we will try to focus on the main differences.

Introduction

For those of us who come to Kotlin from Java, we associate asynchrony with threads. So if that’s the case, you’ve probably spent many hours analyzing locks, race conditions, modifying shared states with synchronized, etc. And yes, sometimes working with asynchrony can be very tedious. So if you’re using Kotlin (or planning to use it), Coroutines have something to do with asynchrony.

Concurrency has existed since the 1960s and 1970s as part of the concurrency model known as “Communicating Sequential Processes.” Kotlin uses a concurrency model based on coroutine and channel primitives, which make understanding concurrency easier. So, in short, we can say that a coroutine is a way to write code asynchronously and non-blocking.

One thing to keep in mind is that concurrency does not mean parallelism at all. We could say that concurrency refers to processes that run independently, while parallelism is the simultaneous execution of tasks.

Concurrency is not parallelism





Differences between Kotlin Coroutines and Java Threads

A coroutine is generally understood as a lighter alternative to Java Threads because it doesn’t actually use threads as they can be used in Java. Instead, it uses a background thread pool. In other words, it is a way to achieve similar results using asynchrony. A coroutine is not associated with any specific thread, as is the case in Java. A coroutine can start on one thread, pause, and resume on another.

The management of coroutines is not handled by the operating system, as is the case with Java Threads, but by Kotlin Runtime. When a Thread is used and it sleeps, it remains blocked for a period of time. During this period, it is not being used or utilized for other tasks, so it cannot be used until the task it was working on is completed. In contrast, with coroutines, we can suspend execution, and the thread that was executing can return to the pool and be used by another coroutine.

Coroutines in Kotlin vs Java Threads
Kotlin Runtime

The best way to see and understand its functioning is to visualize it graphically with examples:

Ejemplo Coroutines y Thread

Let’s create an application with Kotlin and use Maven for dependency management:

Maven dependencias:

		<dependency>
			<groupId>com.fasterxml.jackson.module</groupId>
			<artifactId>jackson-module-kotlin</artifactId>
		</dependency>
		<dependency>
			<groupId>org.jetbrains.kotlin</groupId>
			<artifactId>kotlin-reflect</artifactId>
		</dependency>
		<dependency>
			<groupId>org.jetbrains.kotlin</groupId>
			<artifactId>kotlin-stdlib-jdk8</artifactId>
		</dependency>

		<dependency>
			<groupId>org.jetbrains.kotlinx</groupId>
			<artifactId>kotlinx-coroutines-core</artifactId>
			<version>1.3.9</version>
		</dependency>

In our Maven file, we add the Kotlin dependencies and the coroutine dependency.

Next, we show the necessary code to perform the test with Java threads and Kotlin coroutines.

Example with Java Threads

Let’s create an example to see how it works with threads. In this case, we will create a function that creates a Java Thread in the traditional way, with the only difference being that it is in Kotlin ;).

	fun testThread() {
		println("Test with java thread ")
		
		repeat(10) {

			Thread(Runnable {

				println(" Number ${it}: ${Thread.currentThread().name}")

			}).start()

		}

		Thread.sleep(100)
	}

Output

Test with java thread 
Number 0: Thread-1
Number 1: Thread-2
Number 2: Thread-3
Number 3: Thread-4
Number 4: Thread-5
Number 5: Thread-6
Number 6: Thread-7
Number 8: Thread-9
Number 9: Thread-10
Number 7: Thread-8

As we can see with Java Threads, a separate Thread is generated for each execution in the loop. In this case, there are 10 threads, and the order is not controlled.

Example: Coroutine with Delay

In the following example, we will block the coroutine using the delay function to see that after the blocking, the remaining work is done in different threads.

	fun testDelay() {

		println("Test to delay a coroutine: ")

		repeat(10) {

			GlobalScope.launch {

				println("Before  execution time $it: ${Thread.currentThread().name}")

				delay(10)

				println("After  execution time $it: ${Thread.currentThread().name}")
			}

		}

		Thread.sleep(200)
	}

Output

Test to delay a coroutine: 
Before  execution  0: DefaultDispatcher-worker-1 @coroutine#1
Before  execution  2: DefaultDispatcher-worker-5 @coroutine#3
Before  execution  4: DefaultDispatcher-worker-4 @coroutine#5
Before  execution  5: DefaultDispatcher-worker-2 @coroutine#6
Before  execution  6: DefaultDispatcher-worker-7 @coroutine#7
Before  execution  3: DefaultDispatcher-worker-6 @coroutine#4
Before  execution  7: DefaultDispatcher-worker-8 @coroutine#8
Before  execution  1: DefaultDispatcher-worker-3 @coroutine#2
Before  execution  9: DefaultDispatcher-worker-3 @coroutine#10
Before  execution  8: DefaultDispatcher-worker-7 @coroutine#9
After  execution  2: DefaultDispatcher-worker-4 @coroutine#3
After  execution  6: DefaultDispatcher-worker-8 @coroutine#7
After  execution  4: DefaultDispatcher-worker-1 @coroutine#5
After  execution  3: DefaultDispatcher-worker-3 @coroutine#4
After  execution  0: DefaultDispatcher-worker-6 @coroutine#1
After  execution  7: DefaultDispatcher-worker-2 @coroutine#8
After  execution  5: DefaultDispatcher-worker-7 @coroutine#6
After  execution  1: DefaultDispatcher-worker-5 @coroutine#2
After  execution  9: DefaultDispatcher-worker-3 @coroutine#10
After  execution  8: DefaultDispatcher-worker-3 @coroutine#9

With this example, we can see one of the main differences compared to Java Threads. After the delay, we see that the remaining execution is done in different threads.

Example with Coroutine

In the following example, we will create a simple coroutine using GlobalScope. With this implementation, the lifetime of each coroutine is limited by the application.

Once we have executed the test, we can see that there are 3 different threads during its execution. The thread pool is DefaultDispatcher-worker.

	fun testCoroutine() {

		println("Coroutine test ")

		repeat(10) {

			GlobalScope.launch {

				println("Hi  Number  ${it}: ${Thread.currentThread().name}")

			}

		}

		Thread.sleep(100)
	}

Output

Hi Number  2: DefaultDispatcher-worker-2 @coroutine#3
Hi Number  6: DefaultDispatcher-worker-7 @coroutine#7
Hi Number  7: DefaultDispatcher-worker-8 @coroutine#8
Hi Number  3: DefaultDispatcher-worker-4 @coroutine#4
Hi Number  5: DefaultDispatcher-worker-6 @coroutine#6
Hi Number  1: DefaultDispatcher-worker-3 @coroutine#2
Hi Number  4: DefaultDispatcher-worker-5 @coroutine#5
Hi Number  0: DefaultDispatcher-worker-1 @coroutine#1
Hi Number  8: DefaultDispatcher-worker-3 @coroutine#9
Hi Number  9: DefaultDispatcher-worker-4 @coroutine#10

As can be seen, the messages have been executed in an unpredictable manner. However, we can observe that there are threads that have been repeated, such as 3 and 4. This is one of the differences with Java Threads because, with Threads, there would be 10 and they would not have been reused.

Example with Unconfined Coroutine

In this example, we will use an unconfined dispatcher, which executes the coroutine immediately on the current thread and allows the coroutine to potentially resume on any thread that has used the suspension function.

    fun testCoroutineUncofined() {

        repeat(5) {

            GlobalScope.launch(Dispatchers.Unconfined) {

                println("$it: ${Thread.currentThread().name}")
            }
        }

        Thread.sleep(100)
    }

Output

0: main @coroutine#1
1: main @coroutine#2
2: main @coroutine#3
3: main @coroutine#4
4: main @coroutine#5

By changing the thread pool to use the main thread, we can see that it will always execute sequentially.

Conclusion

In this article, “Coroutines in Kotlin vs Java Threads,” we have seen the main differences between using threads and coroutines, as well as their execution behavior. I hope that the article, as well as the examples, have been useful.

Leave a Reply

Your email address will not be published. Required fields are marked *