多线程入门(够用即可)¶
JS 是单线程的(靠事件循环 + async/await 模拟并发)。Java 是真·多线程——多个线程真的同时跑。本章只讲"写后端够用"的程度。
线程 = 一个独立的执行流¶
但实际开发很少直接 new Thread(开销大、难管理),而是用线程池。
★ 线程池 ExecutorService(重点)¶
ExecutorService pool = Executors.newFixedThreadPool(2); // 2 个线程的池子
// 提交有返回值的任务 → 得到 Future(强烈对标 Promise!)
Future<Integer> future = pool.submit(() -> {
Thread.sleep(100);
return 42;
});
Integer result = future.get(); // 阻塞等结果(对标 await)
pool.shutdown(); // 用完关闭
Future ≈ Promise
pool.submit(task)返回Future—— 对标new Promise。future.get()阻塞等待 —— 对标await。- Java 8 还有
CompletableFuture,能.thenApply()链式调用,更接近 Promise 的用法。
为什么用线程池¶
- 直接
new Thread每次都创建/销毁,开销大。 - 线程池复用固定数量的线程,任务排队执行。
- Spring Boot 内置 Tomcat 就是个线程池:每个 HTTP 请求分配一个线程处理。
synchronized 防并发¶
多个线程同时调 increment(),没有 synchronized 的话 count++ 可能丢数据(它不是原子操作)。加 synchronized = 同一时刻只有一个线程能进入这个方法。
够用清单¶
| 你需要 | 用什么 |
|---|---|
| 跑异步任务拿结果 | ExecutorService.submit() + Future |
| 让任务延迟/定时执行 | ScheduledExecutorService |
| 保护共享数据 | synchronized |
看懂 Spring @Async |
它底层就是提交到线程池 |
虚拟线程在哪?
JDK 21 的虚拟线程让并发代码更简单,但本书基线是 JDK 8。虚拟线程放《第五篇》提一句。
完整可运行示例¶
package com.javaglm.language.ch19;
import java.util.concurrent.*;
/**
* 第 19 章 · 多线程入门(够用即可)。
* 目标:会用线程池、看得懂 Spring 里的异步、亲眼看到"并发竞态"并知道 synchronized 怎么修。
* 不碰 JUC 源码/锁原理——那是进阶,不是写后端的刚需。
* 前端人注意:Java 没有 async/await,并发靠"线程"。
*/
public class ConcurrencyDemo {
public static void main(String[] args) throws Exception {
// 1. ★ 实际开发用线程池:ExecutorService(而不是手动 new Thread)
// 固定 4 个线程的池子,对标"最多并发 4 个任务"
ExecutorService pool = Executors.newFixedThreadPool(4);
// 2. 提交带返回值的任务(Callable),得到 Future(对标 Promise!)
Future<Integer> future = pool.submit(() -> {
Thread.sleep(100); // 模拟耗时操作
return 42;
});
System.out.println("异步任务结果 = " + future.get()); // 阻塞等待结果(对标 await)
pool.shutdown(); // 用完记得关闭(Spring 会帮你管,这里手动示意)
// 3. ★ 并发竞态:8 个线程各自给计数器 +10000,期望最终 80000
// 先看不加锁的:count++ 其实是"读-改-写"三步,多线程交错就会丢更新
Counter unsafe = new UnsafeCounter();
runConcurrent(8, 10000, unsafe);
System.out.println("不加锁 counter = " + unsafe.get() + "(预期 80000,竞态导致丢失更新)");
// 再看 synchronized 加锁的:把 ++ 变成互斥,并发也正确
Counter safe = new SafeCounter();
runConcurrent(8, 10000, safe);
System.out.println("synchronized counter = " + safe.get() + "(预期 80000,加锁后正确)");
}
/** 用一个临时线程池跑 threads 个线程,每个给计数器 increment times 次,跑完关闭池子。 */
private static void runConcurrent(int threads, int times, Counter counter) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(threads);
for (int i = 0; i < threads; i++) {
pool.execute(() -> {
for (int j = 0; j < times; j++) {
counter.increment();
}
});
}
pool.shutdown(); // 不再接受新任务
pool.awaitTermination(5, TimeUnit.SECONDS); // 阻塞等待这批任务全部跑完
}
}
/** 计数器:有无加锁两种实现,共用一个接口方便对比。 */
interface Counter {
void increment();
int get();
}
/** 不加锁:count++ 不是原子操作,并发下会丢失更新。 */
class UnsafeCounter implements Counter {
private int count;
@Override
public void increment() {
count++;
}
@Override
public int get() {
return count;
}
}
/** synchronized 加锁:同一时刻只有一个线程能进 increment,保证 ++ 原子。 */
class SafeCounter implements Counter {
private int count;
@Override
public synchronized void increment() {
count++;
}
@Override
public int get() {
return count;
}
}