多线程
Java 多线程的重点不是会创建 Thread,而是理解并发环境下的可见性、原子性、有序性、线程安全、线程池和锁。企业项目里尤其要重视线程池:JDK 7/8 项目大量使用 ExecutorService、ThreadPoolExecutor、Future,JDK 8 又进一步引入 CompletableFuture 做异步编排,但底层仍然离不开线程池。
学习路线
mermaid
flowchart TD
A["线程创建和生命周期"] --> B["JMM 内存模型"]
B --> C["volatile 与 Atomic"]
C --> D["synchronized 和 Lock"]
D --> E["线程池专题"]
E --> F["CompletableFuture 异步编排"]
F --> G["ThreadLocal 线程隔离"]
G --> H["JUC 工具类"]
H --> I["并发集合和阻塞队列"]
I --> J["并发问题排查"]目录
- Java 内存模型:可见性、原子性、有序性。
- volatile与Atomic:可见性、CAS、原子计数。
- synchronized:对象锁、类锁、锁升级。
- 线程池学习总览:线程池为什么存在、怎么学习。
- ExecutorService:线程池接口和基础使用。
- 线程池核心参数:七大参数、配置含义和错误后果。
- 线程池执行流程原理:核心线程、队列、最大线程、拒绝策略的完整链路。
- 线程池队列与拒绝策略:阻塞队列选择和过载保护。
- 线程池参数估算与监控:CPU/IO 类型、下游容量、队列长度和监控指标。
- 线程池关闭与常见坑:优雅停机、异常吞掉、线程饥饿死锁、ThreadLocal 清理。
- Callable、Future与FutureTask:JDK 7/8 常见异步结果模型。
- CompletableFuture:异步任务编排和结果聚合。
- ThreadLocal:线程隔离变量和内存泄漏。
- JUC并发工具类:CountDownLatch、Semaphore、CyclicBarrier。
- 并发集合与阻塞队列:ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue。
Thread 常用方法
Thread 基础题不要只背方法名,要知道每个方法改变的是“线程是否启动、当前线程是否让出 CPU、是否等待另一个线程、是否发出中断信号”。
| 方法 | 作用 | 容易误解 |
|---|---|---|
start() | 启动新线程,由 JVM 调用该线程的 run() | 不能重复调用同一个线程的 start() |
run() | 普通方法,放线程要执行的逻辑 | 直接调用不会启动新线程 |
sleep(ms) | 当前线程休眠指定时间,不释放已持有的锁 | 它不是让别的指定线程睡眠 |
yield() | 当前线程提示调度器自己愿意让出 CPU | 只是提示,不保证让出成功 |
join() | 当前线程等待目标线程执行结束 | 常用于主线程等待子任务完成 |
interrupt() | 给目标线程设置中断标记,或唤醒阻塞等待 | 不是强制杀死线程 |
isInterrupted() | 判断线程中断标记 | 不会清除标记 |
Thread.interrupted() | 判断当前线程中断标记并清除 | 会清除标记,容易踩坑 |
currentThread() | 获取当前正在执行的线程对象 | 常用于打印线程名和排查 |
setDaemon(true) | 设置守护线程 | 必须在线程 start 前设置 |
start 和 run 的区别
java
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
thread.run(); // main 线程直接执行普通方法
thread.start(); // 新线程执行 run 逻辑mermaid
flowchart TD
A["调用 run()"] --> B["普通方法调用<br/>仍在当前线程执行"]
C["调用 start()"] --> D["请求 JVM 创建新线程"]
D --> E["新线程进入 RUNNABLE"]
E --> F["由新线程执行 run()"]interrupt 为什么不是强制停止
interrupt() 更像是给线程发一个“请停止或请醒来”的信号。线程是否结束,要看线程内部有没有正确响应。
java
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
}
System.out.println("收到中断,准备退出");
});
worker.start();
worker.interrupt();如果线程正在 sleep、wait、join 中,被 interrupt 后通常会抛出 InterruptedException,并清除中断标记。正确做法通常是恢复中断标记或退出循环:
java
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}wait/notify 为什么不是 Thread 的方法
wait()、notify()、notifyAll() 是 Object 的方法,因为它们操作的是对象监视器 monitor。线程必须先持有这个对象的锁,才能调用它们。
java
synchronized (lock) {
lock.wait();
}这解释了为什么 wait 会释放锁,而 sleep 不会释放锁:wait 是 monitor 协作机制的一部分,sleep 只是让当前线程暂停一段时间。
线程生命周期
mermaid
stateDiagram-v2
[*] --> NEW
NEW --> RUNNABLE: start
RUNNABLE --> BLOCKED: 等待锁
RUNNABLE --> WAITING: wait/join/park
RUNNABLE --> TIMED_WAITING: sleep/带超时等待
BLOCKED --> RUNNABLE: 获得锁
WAITING --> RUNNABLE: notify/unpark
TIMED_WAITING --> RUNNABLE: 超时或唤醒
RUNNABLE --> TERMINATED: run结束并发三大问题
| 问题 | 说明 | 解决思路 |
|---|---|---|
| 可见性 | 一个线程修改后,其他线程看不到 | volatile、锁、线程安全容器 |
| 原子性 | 多步操作被线程切换打断 | synchronized、Lock、CAS |
| 有序性 | 指令重排导致执行顺序不符合直觉 | volatile、happens-before |
如果不理解这三个问题,就会误以为“加了 volatile 就线程安全”“用了线程池就一定更快”“ConcurrentHashMap 能保证所有复合操作安全”。
线程池执行流程
mermaid
flowchart TD
A[提交任务] --> B{核心线程是否已满?}
B -->|否| C[创建核心线程执行]
B -->|是| D{队列是否已满?}
D -->|否| E[放入阻塞队列]
D -->|是| F{最大线程是否已满?}
F -->|否| G[创建非核心线程执行]
F -->|是| H[执行拒绝策略]开发建议
- 不要直接无节制创建线程,优先使用线程池。
- 线程池参数要按业务设置,不要所有场景共用一个大线程池。
- 共享变量必须考虑线程安全。
ThreadLocal使用后要及时remove。- 排查并发问题时保留线程名、traceId 和关键状态日志。
代码 Demo:创建线程池
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4,
8,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
executor.submit(() -> {
System.out.println(Thread.currentThread().getName() + " handle task");
});
executor.shutdown();真实项目不要随意使用 Executors.newFixedThreadPool,因为它默认队列可能很大,任务堆积时不容易暴露风险。
