TL;DR
First Initialization Time
Coroutines: Slow
ThreadPool: Fast
Thread: Fast
Thread Switch Time
Coroutines: Fast
ThreadPool: Slow
Thread: Slow
Thread Switch Time
To see the clear difference, I am going to switch between UI Thread to Worker Thread 6 times.
Coroutines:
private fun multiThreadTest_coroutine() {
val start = System.currentTimeMillis()
GlobalScope.launch(Dispatchers.IO) {
Log.d("thread_perf", "Coroutine 1: ${System.currentTimeMillis() - start}")
GlobalScope.launch(Dispatchers.Main) {
Log.d("thread_perf", "Coroutine 2: ${System.currentTimeMillis() - start}")
GlobalScope.launch(Dispatchers.IO) {
Log.d("thread_perf", "Coroutine 3: ${System.currentTimeMillis() - start}")
GlobalScope.launch(Dispatchers.Main) {
Log.d("thread_perf", "Coroutine 4: ${System.currentTimeMillis() - start}")
GlobalScope.launch(Dispatchers.IO) {
Log.d("thread_perf", "Coroutine 5: ${System.currentTimeMillis() - start}")
GlobalScope.launch(Dispatchers.Main) {
Log.d("thread_perf", "Coroutine 6: ${System.currentTimeMillis() - start}")
}
}
}
}
}
}
}Result:
FirstD/thread_perf: Coroutine 1: 32D/thread_perf: Coroutine 2: 40D/thread_perf: Coroutine 3: 41D/thread_perf: Coroutine 4: 42D/thread_perf: Coroutine 5: 42D/thread_perf: Coroutine 6: 43SecondD/thread_perf: Coroutine 1: 0D/thread_perf: Coroutine 2: 2D/thread_perf: Coroutine 3: 2D/thread_perf: Coroutine 4: 3D/thread_perf: Coroutine 5: 4D/thread_perf: Coroutine 6: 4ThirdD/thread_perf: Coroutine 1: 0D/thread_perf: Coroutine 2: 1D/thread_perf: Coroutine 3: 1D/thread_perf: Coroutine 4: 2D/thread_perf: Coroutine 5: 2D/thread_perf: Coroutine 6: 2
When the first Coroutine call is called, it takes some time to initialize the Coroutine library. The actual thread switching time is very fast.

ThreadPool:
val EXECUTOR: Executor = Executors.newCachedThreadPool()
private fun multiThreadTest_threadPool() {
val start = System.currentTimeMillis()
EXECUTOR.execute {
Log.d("thread_perf", "ThreadPool 1: ${System.currentTimeMillis() - start}")
runOnUiThread {
Log.d("thread_perf", "ThreadPool 2: ${System.currentTimeMillis() - start}")
EXECUTOR.execute {
Log.d("thread_perf", "ThreadPool 3: ${System.currentTimeMillis() - start}")
runOnUiThread {
Log.d("thread_perf", "ThreadPool 4: ${System.currentTimeMillis() - start}")
EXECUTOR.execute {
Log.d("thread_perf", "ThreadPool 5: ${System.currentTimeMillis() - start}")
runOnUiThread {
Log.d("thread_perf", "ThreadPool 6: ${System.currentTimeMillis() - start}")
}
}
}
}
}
}
}Result:
FirstD/thread_perf: ThreadPool 1: 2D/thread_perf: ThreadPool 2: 8D/thread_perf: ThreadPool 3: 9D/thread_perf: ThreadPool 4: 9D/thread_perf: ThreadPool 5: 12D/thread_perf: ThreadPool 6: 12SecondD/thread_perf: ThreadPool 1: 0D/thread_perf: ThreadPool 2: 8D/thread_perf: ThreadPool 3: 8D/thread_perf: ThreadPool 4: 9D/thread_perf: ThreadPool 5: 9D/thread_perf: ThreadPool 6: 9ThirdD/thread_perf: ThreadPool 1: 1D/thread_perf: ThreadPool 2: 14D/thread_perf: ThreadPool 3: 14D/thread_perf: ThreadPool 4: 15D/thread_perf: ThreadPool 5: 15D/thread_perf: ThreadPool 6: 15
Thread:
private fun multiThreadTest_thread() {
val start = System.currentTimeMillis()
Thread {
Log.d("thread_perf", "Thread 1: ${System.currentTimeMillis() - start}")
runOnUiThread {
Log.d("thread_perf", "Thread 2: ${System.currentTimeMillis() - start}")
Thread {
Log.d("thread_perf", "Thread 3: ${System.currentTimeMillis() - start}")
runOnUiThread {
Log.d("thread_perf", "Thread 4: ${System.currentTimeMillis() - start}")
Thread {
Log.d("thread_perf", "Thread 5: ${System.currentTimeMillis() - start}")
runOnUiThread {
Log.d("thread_perf", "Thread 6: ${System.currentTimeMillis() - start}")
}
}.start()
}
}.start()
}
}.start()
}Result:
FirstD/thread_perf: Thread 1: 1D/thread_perf: Thread 2: 13D/thread_perf: Thread 3: 15D/thread_perf: Thread 4: 15D/thread_perf: Thread 5: 17D/thread_perf: Thread 6: 17SecondD/thread_perf: Thread 1: 1D/thread_perf: Thread 2: 9D/thread_perf: Thread 3: 11D/thread_perf: Thread 4: 11D/thread_perf: Thread 5: 12D/thread_perf: Thread 6: 13ThirdD/thread_perf: Thread 1: 1D/thread_perf: Thread 2: 14D/thread_perf: Thread 3: 15D/thread_perf: Thread 4: 15D/thread_perf: Thread 5: 16D/thread_perf: Thread 6: 17
Conclusion
Coroutine takes longer at the very first call. However, it becomes significantly shorter to switch after the first.
Coroutines is the next gen excellent Library. However, it comes with the price of the initialization time. We try to use Coroutine as much as possible. However, just to maximize the initialization performance, we are avoiding to use it during the initialization: application.onCreate(), and the launcher activity.onCreate().
Not much difference between Thread & ThreadPool (They will eventually make a difference when more and more thread switches happens)


