为何Kotlin中(1..100).map{launch{...}}.forEach{it.join()}可并行执行?
Great question! This is a perfect example of how Kotlin's coroutine model differs fundamentally from Java's raw thread handling—let's break down exactly why the behaviors are so different.
First, Let's Confirm the Java Scenario (Serial Execution)
You’re spot-on about the Java case. If you wrote code that starts a thread and immediately calls join() like this:
for (int i = 1; i <= 100; i++) { Thread thread = new Thread(() -> { System.out.println("Hello from " + Thread.currentThread().getName()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }); thread.start(); thread.join(); // Blocks the main thread right away }
This runs one thread at a time because Thread.join() is a blocking call. When you invoke it, the main thread stops entirely and waits for that single worker thread to finish its work. It can’t start the next thread until the current one is done—so no parallelism here.
Why the Kotlin Code Achieves Parallelism
Your Kotlin code uses coroutines, not raw threads, and that’s where the key differences lie:
1. launch Creates Lightweight Coroutines, Not Threads
When you call launch { ... }, you’re not spawning a new thread immediately. Instead, you’re creating a coroutine—a lightweight, suspendable task managed by a coroutine dispatcher (by default, Dispatchers.Default, which uses a shared thread pool). Multiple coroutines can run on the same thread, and the dispatcher handles switching between them efficiently without the overhead of thread creation.
2. Job.join() Suspends the Coroutine, Not the Thread
The join() method you’re calling on the Job returned by launch is nothing like Thread.join(). It’s a suspending function:
- When you call
join(), only the current coroutine (the one running theforEachloop) pauses its execution. - The underlying thread that was running this coroutine is now free to execute other coroutines that you’ve already launched (all 100 of them, in your case).
- Once the target coroutine finishes its work (prints the message and waits 100ms), the paused loop coroutine resumes and moves on to the next
join().
3. Let’s Walk Through the Execution Flow
Here’s what happens step-by-step in your Kotlin code:
- Launch all coroutines first:
(1..100).map { launch { ... } }spins up all 100 coroutines almost instantly. The dispatcher adds them to its queue and starts executing them on available threads in the pool. - Wait for each coroutine (without blocking): The
forEach { it.join() }loop iterates over eachJob:- For the first job,
join()suspends the loop coroutine. The thread it was using can now run other waiting coroutines. - As each coroutine finishes, the corresponding
join()call resumes, and the loop moves to the next job.
- For the first job,
- Parallel execution: Since all coroutines are launched up front and the loop only suspends (not blocks), all 100 coroutines run in parallel across the thread pool.
Key Takeaway
The core distinction is:
- Java’s
Thread.join()blocks the entire thread, halting all work on that thread until the target thread completes. - Kotlin’s coroutine
Job.join()suspends only the current coroutine, allowing the thread to handle other tasks while waiting.
内容的提问来源于stack exchange,提问作者neuron

