Kotlin Coroutines vs JVM Threads

Hardik Mehta
MobilePeople
Published in
6 min readJul 11, 2021

--

Photo by Anya Smith on Unsplash

We all have heard that “coroutines are lightweight threads”
One would probably ask why creating coroutine is much cheaper than creating threads. This article aims to differentiate these 2 entities.

Before you delve into the comparison, you need to know about the difference between Concurrency and parallelism
For this, please go through the concurrency vs parallelism article once…

If you are already aware of the need for multiple threads and Android’s way of performing async operation then please jump to the Coroutine section

Let us first understand,

Why do you need multiple threads in the first place?

When an application is launched, a process is created with one single thread initially, called as Main thread. It is responsible for drawing views, invoking the click listeners, launching Activities, Broadcast receiver, and Service.

Consider that you need to perform a long-running operation that requires input from some other resource for example reading a file, making network calls, performing heavy database operations which would eventually block the main thread, and being a good developer you do not want to do that otherwise OS will show ANR dialog. Here arises a need to have more than one thread to get the work done. We dedicate the long-running/time-consuming tasks to other threads (worker thread) so that the main/UI thread can continue doing its work. This helps make the UI responsive and we can achieve the max throughput of the available system cores.

Note: More the number of cores, the less number of application each core has to worry about and that generally improves the device performance. They are very common on mobiles now.

Android’s way of multithreading

Android has its own arsenal to accomplish this long-running task on non UI thread such as Async task(deprecated), Handler, ThreadPoolExecutor, Service/IntentService + third party library like RxAndroid library. Each Asynchronous programming mechanism has its own way of providing the result to UI if needed, for example, callbacks, post methods, registering a broadcast receiver, etc. A number of mechanisms have multi-phased and extensive learning curves, while others require tons of boilerplate codes and callbacks. In both these cases, the developers’ time is wasted unfairly. 😐

Here comes Coroutine ( Cooperative + functions)

Coroutine represents cooperatively multitasking that allows you to write blocking code as sequential code while replacing the need for result handing mechanisms like callbacks. Here, the word cooperative means tasks voluntarily yield in order to allow other tasks to run.

Coroutine has following advantages in comparison with the above-mentioned components.

Non-blocking:
A thread may get blocked because of wait() call; it will not become runnable again until it gets the notify() or notifyAll() message or if it is waiting for some I/O to complete or sleep(timeInMilis) is invoked. Meaning you can’t use it anymore until it finishes its work.

Relatively, you can run many coroutines on a single thread due to support for suspension, which means that upon coroutine suspension, the thread on which coroutine ran is returned to a pool and may be used by another coroutine if it is waiting. when the suspension is over for the former coroutine, it resumes on a free thread from the pool. This way with fewer threads and less memory usage, more concurrent work can be done.

Thread.sleep vs Coroutines.delay

In above example, both the coroutines run on Main thread. When Thread.sleep(2000L) is put inside first launch. The output is

Coroutine 1 main 
Coroutine 2 main

This is because, when first coroutine is executed it will come at sleep() statement which in turns block the main thread. So, main thread is blocked and it will not serve other threads for 2 seconds.

Now run the function again after replacing sleep()statement with delay(). The output will be

Coroutine 2 main 
Coroutine 1 main

This is because, when first coroutine is executed it will come at delay() which suspends the coroutine for defined seconds. The main Thread will not wait for defined seconds rather it will start executing other coroutines.
Hence, it will execute coroutine 2 and prints Coroutine 2 main first and after the defined seconds will go back to first coroutine and print Coroutine 1 main

Lightweight:
Unlike JVM threads which are mapped to a native thread, the compiler transforms the coroutine to a state machine. This state machine is capable of handling suspensions while maintaining their state using a hidden parameter passed to the function named continuation object. This technique is “Continuation passing style
Thanks to this, they don’t need context switching on the processor, making them faster.

PS : I know it will be difficult to comprehend this concept at single go. It will take a separate article to explain how the technique is implemented internally. I found an article by Ashish helpful. I request you to read the official documentation or any article relating to the concept.

Coroutines are not managed by the operating system, but by the Kotlin Runtime.

Suspending a coroutine saves memory as compare to blocking a thread at the same time supporting many concurrent operations

Preemption vs Collaboration:
Context switching involves storing and restoring the state of a process or a thread. This dispatch latency is costly. It is advised to avoid unnecessary context switching since the processor remains idle for a fraction of time.

Threads are pre-emptive.
The OS kernel has an algorithm called Scheduler, whose aim is to maximise the throughput while minimising the wait time. OS uses is to pre-empt the running threads. It is responsible for switching between the running threads. This switching is an expensive operation in terms of CPU cycles consumed. 😟

Coroutine switches are cooperative.
The programmer and kotlin’s runtime controls when a switch will happen. The OS kernel is not involved in the coroutine switches. So it can be viewed as invoking a regular function. By avoiding unnecessary switches we can attain efficiency .

The big benefit in cooperative scheduling over preemptive is that the former does not use “context switching”.

Imagine this scenario as a coroutine is passing the baton to other coroutines in a more elegant or graceful way.

Cost of creation :
A java thread consumes a considerable amount of resources, typically 1 MB. The size is the combination of its stack, thread-local storage, and the cost of thread scheduling/context-switching/CPU cache invalidation. This seems less initially but will be a point of consideration if you end up having many threads. That is why you cannot just create thousands of threads. If you do, you are likely to run out of memory.

A coroutine, on the other hand, is purely a user-level language abstraction. It does not tie any native resources rather it uses just one relatively small object in the JVM heap. Thus fewer memory footprints comparatively. Now you know why it is easy to create 100k coroutines without compromising the system performance.

To conclude:

Threads are pretty heavy in terms of memory usage and context-switching.

Higher memory usage = higher CPU usage as creating objects is an expensive operation and the GC needs to run more frequently to get rid of unused objects.

This sequence ultimately results in higher battery consumption and a less smooth UI for the user. 😟

Reduced memory leaks and support of cancellation:
Coroutine uses a structured concurrency strategy for running operations in a given scope. When the scope to which it is bound is finished, the coroutine is stopped. Eventually preventing the work leak.

I hope this article explained the difference between them satisfactorily.

This is my first article so kindly help rectify any error or share your views in the comments below.

--

--