跪拜 Guibai
← All articles
Android

Coroutines Don't Beat Threads on Speed — They Beat Asynchronous Complexity

By 潜龙勿用之化骨龙 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Android teams that treat coroutines as just a faster thread pool miss the architectural win. The real savings come from deleting CountDownLatch, Future chains, and lifecycle-management boilerplate — code that was never about the product and always about taming asynchrony.

Summary

The standard pitch — coroutines are lightweight threads that switch in user space — misses the point. Thread pools, Netty, and Node.js already solved thread reuse and high concurrency. What coroutines actually fix is the organizational cost of asynchronous programming: they turn callback chains into compiler-managed continuations and state machines, so business logic reads top-to-bottom again without blocking a thread.

Under the thread model, writing linear async code meant reaching for CountDownLatch, Future.get(), or BlockingQueue — tools that exist solely to stitch callbacks back into a straight line. Those synchronizers bring their own overhead: AQS internals, lock contention, and parked threads. Coroutines replace that entire layer with suspension points and resume, so the code stays linear while the underlying thread is released.

The shift goes deeper than syntax. Structured concurrency binds task lifecycles to owner scopes, eliminating leaked background work. Shared mutable state moves toward single-owner patterns backed by Channel and Flow instead of synchronized blocks. The result is fewer locks, fewer waiting threads, and less code that exists only to coordinate threads rather than express business rules.

Takeaways
Coroutines are not faster than threads; they reduce the organizational cost of writing asynchronous code.
Thread pools, Netty, and Node.js already proved that thread count and user-space switching are not the bottleneck coroutines solve.
Callback hell forces developers to manually maintain state machines; coroutines hand that job to the compiler via Continuation and suspend.
Thread-era linear async code relied on JUC synchronizers (CountDownLatch, Future.get, BlockingQueue) that block threads and add coordination overhead.
A suspend function decompiles to a Continuation-passing style — callbacks still exist, but the compiler manages them instead of the developer.
Structured concurrency binds task lifecycles to scopes like ViewModelScope, so cancellation propagates automatically and background leaks disappear.
Coroutines encourage single-owner shared state (Channel, Flow, Actor) over multi-threaded lock contention, reducing the need for synchronized and ReentrantLock.
Mutex in coroutines suspends the coroutine rather than blocking the thread, which avoids thread-pool starvation and WAITING-thread pileups.
The true value proposition is linear expression plus non-blocking execution — synchronous readability without synchronous cost.
Conclusions

The article reframes the entire coroutine-vs-thread debate away from performance benchmarks and toward developer ergonomics — a perspective that explains why Kotlin coroutines feel transformative even when raw throughput is similar.

Pointing out that Netty and Node.js already solved high-concurrency connection handling undermines the common 'lightweight thread' marketing and forces the conversation toward code structure, which is where coroutines actually win.

The claim that coroutines reduce the *need* for locks — not just make locking cheaper — is underappreciated. Single-owner patterns with Channel and Flow eliminate entire categories of concurrency bugs.

Calling out that suspend functions still compile to callbacks is a useful demystification: coroutines are syntactic and runtime infrastructure over callback-passing, not magic that deletes asynchrony.

Concepts & terms
Continuation
A compiler-generated callback interface that represents the rest of a suspend function's execution. When a coroutine suspends, the current state is saved into a Continuation; when the async work completes, the Continuation is invoked to resume execution from the suspension point.
Structured Concurrency
A concurrency model where the lifecycle of child coroutines is scoped to a parent coroutine. Cancelling the parent automatically cancels all children, preventing leaked background work and making task lifetimes predictable.
Single Owner / Actor Model
A concurrency pattern where shared mutable state is owned by a single coroutine. Other coroutines communicate via message-passing channels (Channel, Flow) instead of competing for locks, eliminating data races by design.
JUC Synchronizers
Java's java.util.concurrent synchronization utilities — CountDownLatch, Future, CompletableFuture, BlockingQueue, Semaphore, etc. — commonly used in thread-based code to coordinate asynchronous work, often at the cost of blocking threads.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗