第 10 章 · 多线程与并发基础
本章目标:理解 Thread 与 Runnable 创建线程的方式;使用 ExecutorService 线程池管理任务;掌握 synchronized 同步入门;了解 volatile 与可见性;与 Python 多线程对照,明确 Java 线程适用场景。
学时建议:5~6 小时(含 2 小时跟练)
前置:ch05 类与对象;ch08 集合(注意线程安全)。
10.1 进程与线程
进程(Process)— 资源分配单位,独立内存空间
└── 线程(Thread)— CPU 调度单位,共享进程内存
| 概念 | 说明 |
|---|---|
| 多线程 | 同一进程内并发执行多个任务 |
| 并发 | 逻辑上同时(单核切换) |
| 并行 | 物理上同时(多核) |
toolkit-demo 场景:同时解析多个日志文件、批量导出 CSV 时,可用线程池并行 IO。
10.2 创建线程:Thread 与 Runnable
方式一:继承 Thread(少用)
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行: " + getName());
}
}
MyThread t = new MyThread();
t.start(); // 启动线程,勿直接调用 run()
方式二:实现 Runnable(推荐)
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
};
Thread t1 = new Thread(task, "worker-1");
t1.start();
| 要点 | 说明 |
|---|---|
start() | 启动新线程执行 run() |
直接 run() | 仅在当前线程执行,无并发 |
| 线程名 | 便于日志排查 |
10.3 线程生命周期
新建 → 就绪 → 运行 → 阻塞/等待 → 终止
↑___________|
| 状态 | 触发 |
|---|---|
NEW | 创建未 start |
RUNNABLE | 可运行 |
BLOCKED | 等待锁 |
WAITING | wait() / join() |
TERMINATED | 执行完毕 |
Thread t = new Thread(() -> System.out.println("done"));
t.start();
t.join(); // 主线程等待 t 结束
System.out.println("主线程继续");
10.4 ExecutorService 线程池
手动 new Thread 难以管理数量与复用;线程池统一调度:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class PoolDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
int taskId = i;
pool.submit(() -> {
System.out.println("任务 " + taskId + " 由 " +
Thread.currentThread().getName() + " 执行");
});
}
pool.shutdown(); // 不再接受新任务
pool.awaitTermination(30, TimeUnit.SECONDS);
System.out.println("全部完成");
}
}
| 工厂方法 | 特点 |
|---|---|
newFixedThreadPool(n) | 固定 n 线程,任务队列 |
newCachedThreadPool() | 线程数弹性,适合短任务 |
newSingleThreadExecutor() | 单线程顺序执行 |
生产环境:优先使用 ThreadPoolExecutor 显式配置核心数、队列、拒绝策略,避免 Executors 无界队列 OOM。
10.5 Callable 与 Future
需要返回值时用 Callable:
import java.util.concurrent.*;
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> future = pool.submit(() -> {
Thread.sleep(500);
return 42;
});
System.out.println("等待结果...");
int result = future.get(); // 阻塞直到完成
System.out.println(result);
pool.shutdown();
| API | 说明 |
|---|---|
submit(Callable) | 返回 Future |
future.get() | 阻塞取结果 |
future.get(timeout, unit) | 限时等待 |
invokeAll(tasks) | 批量提交 |
10.6 共享数据与 synchronized
多线程访问同一变量需同步,否则出现竞态:
public class Counter {
private int count = 0;
// 不安全版本
public void incrementUnsafe() {
count++;
}
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Counter c = new Counter();
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
pool.submit(() -> c.increment());
}
pool.shutdown();
pool.awaitTermination(10, TimeUnit.SECONDS);
System.out.println(c.getCount()); // 应为 1000
synchronized:
- 修饰方法:锁当前对象(实例方法)或类(静态方法)
- 同步块:
synchronized (obj) { ... },缩小锁粒度
| 问题 | synchronized 作用 |
|---|---|
| 竞态条件 | 同一时刻仅一线程进入 |
| 内存可见性 | 释放锁时刷新主内存 |
10.7 锁对象与同步块
public class BankAccount {
private final Object lock = new Object();
private double balance;
public void deposit(double amount) {
synchronized (lock) {
balance += amount;
}
}
public double getBalance() {
synchronized (lock) {
return balance;
}
}
}
原则:锁的对象应私有、不可变;避免锁字符串字面量或 this 被外部访问。