前言
在当今互联网高并发的背景下,并发编程已成为Java开发者的必备技能。从简单的多线程应用到复杂的分布式系统,线程安全问题无处不在。本文将从进程与线程的基础概念出发,深入剖析synchronized和volatile的底层原理,结合电商业务场景,带你系统掌握Java并发编程的核心知识。
一、理论知识与核心概念
1.1 进程与线程的区别
进程(Process) 是操作系统进行资源分配和调度的基本单位,拥有独立的内存空间。每个进程都有自己独立的代码段、数据段和堆栈段。
线程(Thread) 是CPU调度和执行的最小单位,是进程中的一个执行单元。同一进程内的多个线程共享进程的内存空间(堆内存、方法区),但每个线程有自己独立的栈空间。
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源占用 | 独立内存空间,开销大 | 共享进程内存,开销小 |
| 通信方式 | IPC(管道、消息队列、共享内存) | 直接读写共享变量 |
| 创建速度 | 慢(需要分配独立内存) | 快(只需分配栈空间) |
| 影响范围 | 进程崩溃不影响其他进程 | 线程崩溃可能导致整个进程崩溃 |
| 上下文切换 | 开销大(需要切换内存空间) | 开销小(共享内存空间) |
1.2 线程的创建方式
Java提供了四种创建线程的方式:
java
// 方式1: 继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread by extending Thread: " + Thread.currentThread().getName());
}
}
// 方式2: 实现Runnable接口(推荐)
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread by Runnable: " + Thread.currentThread().getName());
}
}
// 方式3: 实现Callable接口(有返回值)
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Thread by Callable: " + Thread.currentThread().getName();
}
}
// 方式4: 线程池(生产环境推荐)
public class ThreadPoolExample {
private static final ExecutorService executor =
new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "custom-pool-" + threadNumber.getAndIncrement());
}
},
new ThreadPoolExecutor.CallerRunsPolicy());
}
对比分析:
- 继承Thread: 由于Java单继承限制,扩展性差,不推荐
- 实现Runnable: 解耦任务与线程,可以被线程池重用,推荐使用
- 实现Callable: 可以返回结果和抛出异常,适合需要返回值的场景
- 线程池: 避免频繁创建销毁线程,复用线程资源,生产环境必选
1.3 线程的生命周期
Java线程有6种状态,定义在Thread.State枚举中:
scss
┌─────────┐
│ NEW │ (新建状态: Thread对象创建,但未调用start())
└────┬────┘
│ start()
▼
┌──────────────┐
│ RUNNABLE │ (可运行状态: 包含就绪Ready和运行Running两种子状态)
└──────┬───────┘
│
├──────► BLOCKED (阻塞状态: 等待获取synchronized锁)
│
├──────► WAITING (无限期等待: Object.wait()、Thread.join()、LockSupport.park())
│
├──────► TIMED_WAITING (限期等待: Thread.sleep()、Object.wait(timeout)、Thread.join(timeout))
│
▼
┌──────────────┐
│ TERMINATED │ (终止状态: run()方法执行完毕或抛出异常)
└──────────────┘
关键状态转换:
- NEW → RUNNABLE : 调用
start()方法 - RUNNABLE → BLOCKED: 尝试获取synchronized锁失败,进入锁的EntryList队列
- RUNNABLE → WAITING : 调用
Object.wait()、Thread.join()、LockSupport.park() - RUNNABLE → TIMED_WAITING : 调用
Thread.sleep(timeout)、Object.wait(timeout)等带超时的方法 - BLOCKED/WAITING/TIMED_WAITING → RUNNABLE: 获取到锁、被notify唤醒、超时时间到
- RUNNABLE → TERMINATED: run()方法执行完毕
1.4 并发与并行
并发(Concurrency) : 单核CPU通过时间片轮转,快速切换执行多个任务,看起来像同时执行,实际上是交替执行。强调的是任务调度能力。
并行(Parallelism) : 多核CPU真正同时执行多个任务,每个核心独立运行一个线程。强调的是同时执行能力。
less
并发(单核CPU):
时间 ───┬───┬───┬───┬───┬───┬───┬───►
任务A █ █ █
任务B █ █ █
任务C █ █ █
并行(多核CPU):
时间 ───────────────────────────►
核心1 █████████████████████ (任务A)
核心2 █████████████████████ (任务B)
核心3 █████████████████████ (任务C)
1.5 线程安全的定义
线程安全是指多个线程访问同一个对象时,无论运行时环境采用何种调度方式或这些线程如何交替执行,该对象都能表现出正确的行为,并且不需要调用方提供额外的同步措施。
线程安全的三大特性:
- 原子性(Atomicity): 一个或多个操作要么全部执行成功,要么全部不执行
- 可见性(Visibility): 一个线程修改了共享变量,其他线程能立即看到修改后的值
- 有序性(Ordering): 程序执行的顺序按照代码的先后顺序执行
判断线程安全的标准:
- 多线程环境下,对共享变量的操作不会导致数据不一致
- 不会出现丢失更新、脏读、不可重复读等问题
- 程序运行结果与单线程环境保持一致
二、原理深度剖析
2.1 synchronized原理详解
synchronized是Java提供的内置锁机制,用于解决多线程并发访问共享资源的同步问题。在JDK 1.6之前,synchronized是重量级锁,性能较差。JDK 1.6对synchronized进行了大量优化,引入了偏向锁、轻量级锁、自旋锁、锁消除、锁粗化等技术。
2.1.1 对象头与Mark Word结构
在HotSpot虚拟机中,对象在内存中的布局分为三个区域:
scss
┌─────────────────────────────────────────────────┐
│ Java对象内存布局 │
├─────────────────────────────────────────────────┤
│ 1. 对象头(Object Header) │
│ ├─ Mark Word (8字节,64位JVM) │
│ └─ Class Pointer (4/8字节,指向类元数据) │
│ 2. 实例数据(Instance Data) │
│ 3. 对齐填充(Padding) │
└─────────────────────────────────────────────────┘
Mark Word在不同锁状态下的结构(64位JVM):
| 锁状态 | 25bit | 31bit | 1bit | 4bit | 1bit(是否偏向锁) | 2bit(锁标志位) |
|---|---|---|---|---|---|---|
| 无锁状态 | unused | hashcode | unused | age | 0 | 01 |
| 偏向锁 | threadID(54bit) | epoch(2bit) | unused | age | 1 | 01 |
| 轻量级锁 | 指向栈中Lock Record的指针(62bit) | 00 | ||||
| 重量级锁 | 指向互斥量(Monitor)的指针(62bit) | 10 | ||||
| GC标记 | 空 | 11 |
字段说明:
- hashcode: 对象的identity hash code
- age: 对象的分代年龄(最大15,超过15晋升老年代)
- threadID: 持有偏向锁的线程ID
- epoch: 偏向时间戳,用于批量重偏向
2.1.2 偏向锁(Biased Locking)
设计初衷:
在大多数情况下,锁不存在竞争,总是由同一个线程多次获得。为了减少同一线程获取锁的代价(CAS操作也有开销),引入了偏向锁。
偏向锁的获取流程:
objectivec
┌─────────────────────────────────────────────────┐
│ 偏向锁获取流程 │
└─────────────────────────────────────────────────┘
线程访问同步块
│
▼
检查Mark Word是否为可偏向状态(锁标志位01,偏向锁标志1)
│
├─ YES ─► 检查threadID是否指向当前线程
│ │
│ ├─ YES ─► 直接执行同步块(无需CAS)
│ │
│ └─ NO ─► CAS将Mark Word的threadID改为当前线程ID
│ │
│ ├─ 成功 ─► 获得偏向锁,执行同步块
│ │
│ └─ 失败 ─► 发生竞争,撤销偏向锁,膨胀为轻量级锁
│
└─ NO ─► 检查是否允许偏向
│
├─ 允许 ─► CAS尝试获取偏向锁
│
└─ 不允许 ─► 直接使用轻量级锁
偏向锁的撤销:
偏向锁在以下情况下会撤销:
- 有其他线程尝试获取锁: 当前持有偏向锁的线程A在运行,线程B尝试获取该锁
- 调用wait/notify方法: 偏向锁不支持这些操作
- 对象的identityHashCode被调用: Mark Word需要存储hashcode,没有空间存储threadID
撤销过程(需要等待全局安全点 Safepoint):
java
// 撤销偏向锁的过程(简化)
1. 暂停持有偏向锁的线程(STW - Stop The World)
2. 检查持有偏向锁的线程是否还存活
- 如果线程已经退出同步块 → 撤销偏向锁,恢复到无锁状态
- 如果线程还在同步块内 → 升级为轻量级锁
3. 唤醒暂停的线程
批量重偏向与批量撤销:
- 批量重偏向: 当一个类的对象被多个线程访问,但不存在竞争时,JVM会对该类的所有对象进行批量重偏向(更新epoch)
- 批量撤销: 当撤销偏向锁的次数超过阈值(默认40次),JVM会认为该类不适合偏向锁,直接撤销该类所有对象的偏向锁
JDK 15后默认禁用偏向锁的原因:
- 偏向锁的撤销需要进入安全点(STW),在高并发场景下反而降低性能
- 现代应用程序中,真正无竞争的场景越来越少
- JIT编译器和锁消除技术的进步,降低了偏向锁的收益
2.1.3 轻量级锁(Lightweight Locking)
适用场景:
线程交替执行同步块,不存在真正的竞争。通过CAS操作避免使用互斥量(Mutex),减少操作系统层面的线程阻塞。
轻量级锁的获取流程:
less
┌─────────────────────────────────────────────────┐
│ 轻量级锁获取流程 │
└─────────────────────────────────────────────────┘
1. 线程进入同步块前,在栈帧中创建Lock Record
┌──────────────┐
│ Lock Record │
├──────────────┤
│ Displaced │ ← 存储对象原始的Mark Word
│ Mark Word │
├──────────────┤
│ Owner指针 │ → 指向锁对象
└──────────────┘
2. CAS将对象的Mark Word替换为指向Lock Record的指针
对象Mark Word: [Lock Record指针 | 00]
│
└─────► 指向栈帧中的Lock Record
3. CAS替换结果:
├─ 成功 ─► 获得轻量级锁,执行同步块
│
└─ 失败 ─► 检查Mark Word是否指向当前线程的栈帧
│
├─ 是 ─► 锁重入,再次创建Lock Record
│
└─ 否 ─► 发生竞争,自旋尝试获取锁
│
└─ 自旋次数超过阈值 ─► 膨胀为重量级锁
自旋优化(Spin Lock):
当线程获取轻量级锁失败时,不立即阻塞,而是执行忙循环(自旋)等待,避免用户态到内核态的切换。
java
// 自旋等待示例(伪代码)
int spinCount = 0;
while (!tryAcquireLock()) {
spinCount++;
if (spinCount > MAX_SPIN_COUNT) {
// 自旋次数超过阈值,膨胀为重量级锁
inflateToHeavyweightLock();
break;
}
// CPU空转,等待锁释放
// 可能执行PAUSE指令,降低CPU功耗
}
自适应自旋(Adaptive Spinning):
JDK 1.6引入,自旋次数不再固定:
- 如果上一次自旋成功获得锁,允许更长的自旋时间
- 如果上一次自旋失败,缩短自旋时间或直接阻塞
轻量级锁的释放:
java
1. CAS将Lock Record中的Displaced Mark Word替换回对象头
2. CAS替换结果:
├─ 成功 ─► 释放锁成功
└─ 失败 ─► 说明有其他线程在竞争,锁已膨胀为重量级锁,需要唤醒阻塞的线程
2.1.4 重量级锁(Heavyweight Locking)
当轻量级锁自旋一定次数后仍未获取到锁,锁会膨胀为重量级锁。重量级锁依赖操作系统的Mutex Lock(互斥量)实现,会导致线程在用户态和内核态之间切换。
Monitor对象结构:
yaml
┌─────────────────────────────────────────────────┐
│ ObjectMonitor结构 │
├─────────────────────────────────────────────────┤
│ _owner: 持有锁的线程 │
│ _EntryList: 等待锁的线程队列(BLOCKED状态) │
│ _WaitSet: 调用wait()方法的线程队列(WAITING状态)│
│ _count: 锁的重入次数 │
│ _recursions: 重入计数器 │
└─────────────────────────────────────────────────┘
重量级锁的状态转换:
┌──────────┐
│ Thread1 │ (获得锁,_owner指向Thread1)
└────┬─────┘
│ synchronized(obj)
▼
┌──────────┐
│ _owner │ ────► Thread1
└──────────┘
┌──────────┐
│_EntryList│ ◄──── Thread2, Thread3 (尝试获取锁,进入BLOCKED状态)
└──────────┘
┌──────────┐
│ _WaitSet │ ◄──── Thread4 (调用wait(),进入WAITING状态)
└──────────┘
重量级锁的获取与释放流程:
markdown
获取流程:
1. 线程尝试获取锁(CAS修改_owner为当前线程)
2. 如果_owner为空,获取成功
3. 如果_owner指向当前线程,锁重入(_count++)
4. 如果_owner指向其他线程,进入_EntryList,线程阻塞(BLOCKED状态)
释放流程:
1. _count-- (处理锁重入)
2. 如果_count == 0,释放锁(_owner置为NULL)
3. 从_EntryList中唤醒一个线程(notify)
4. 被唤醒的线程从BLOCKED状态转为RUNNABLE状态,重新竞争锁
2.1.5 锁升级的完整过程
makefile
┌─────────────────────────────────────────────────────────────┐
│ synchronized锁升级过程 │
└─────────────────────────────────────────────────────────────┘
无锁状态 偏向锁 轻量级锁 重量级锁
│ │ │ │
│ CAS获取 │ 有其他线程 │ 自旋失败 │
│ 偏向锁 │ 竞争锁 │ 或竞争激烈 │
├────────────────►├──────────────────►├────────────────►│
│ │ │ │
锁标志位: 01 锁标志位: 01 锁标志位: 00 锁标志位: 10
偏向标志: 0 偏向标志: 1
性能: 最快 性能: 快 性能: 较快 性能: 慢
开销: 无 开销: CAS 开销: CAS+自旋 开销: 用户态/内核态切换
适用: 无竞争 适用: 单线程反复 适用: 交替执行 适用: 高竞争
获取同一把锁 少量竞争 多线程竞争激烈
性能对比:
| 锁类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 偏向锁 | 加锁解锁无需额外消耗 | 有线程竞争会带来额外的锁撤销消耗 | 只有一个线程访问同步块 |
| 轻量级锁 | 竞争线程不阻塞,响应速度快 | 自旋消耗CPU | 线程交替执行同步块,竞争不激烈 |
| 重量级锁 | 不消耗CPU(线程阻塞) | 线程阻塞,响应时间慢 | 竞争激烈,同步块执行时间长 |
2.2 volatile关键字与Java内存模型(JMM)
2.2.1 Java内存模型(JMM)基础
Java内存模型(Java Memory Model)定义了线程和主内存之间的抽象关系,规定了一个线程如何以及何时可以看到其他线程修改的共享变量的值。
JMM抽象结构:
bash
┌────────────────────────────────────────────────────────────┐
│ Java内存模型(JMM) │
└────────────────────────────────────────────────────────────┘
线程A 线程B
┌─────────┐ ┌─────────┐
│ 工作内存 │ │ 工作内存 │
│ (本地) │ │ (本地) │
│ ┌─────┐ │ │ ┌─────┐ │
│ │变量a│ │ │ │变量b│ │
│ │的副本│ │ │ │的副本│ │
│ └─────┘ │ │ └─────┘ │
└────┬────┘ └────┬────┘
│ read/load │ read/load
│ ↓ │ ↓
│ store/write │ store/write
│ ↑ │ ↑
┌────┴──────────────────────────────────────────────┴────┐
│ 主内存(共享) │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │变量a│ │变量b│ │变量c│ │ ... │ │变量n│ │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
└──────────────────────────────────────────────────────┘
内存交互操作(8种原子操作):
- lock(锁定): 作用于主内存的变量,标识为线程独占状态
- unlock(解锁): 作用于主内存的变量,释放独占状态
- read(读取): 作用于主内存的变量,把值传输到工作内存
- load(载入): 作用于工作内存的变量,把read的值放入工作内存的变量副本
- use(使用): 作用于工作内存的变量,把值传递给执行引擎
- assign(赋值): 作用于工作内存的变量,把执行引擎的值赋给工作内存的变量
- store(存储): 作用于工作内存的变量,把值传送到主内存
- write(写入): 作用于主内存的变量,把store的值写入主内存的变量
happens-before原则(8大规则):
happens-before原则定义了操作之间的偏序关系,如果操作A happens-before操作B,那么A的执行结果对B可见。
- 程序次序规则: 单线程内,按照代码顺序执行
- 监视器锁规则: unlock操作 happens-before 后续对同一个锁的lock操作
- volatile变量规则: volatile写操作 happens-before 后续对该变量的读操作
- 线程启动规则: Thread.start() happens-before 该线程的每一个动作
- 线程终止规则: 线程的所有操作 happens-before 其他线程检测到该线程终止(Thread.join()返回、Thread.isAlive()返回false)
- 线程中断规则: 线程interrupt()方法的调用 happens-before 被中断线程检测到中断事件发生
- 对象终结规则: 对象的构造函数执行结束 happens-before finalize()方法
- 传递性: 如果A happens-before B,B happens-before C,那么A happens-before C
2.2.2 volatile的两大特性
1. 可见性保证
当一个变量被声明为volatile时,对该变量的写操作会立即刷新到主内存,对该变量的读操作会从主内存中读取最新值。
java
public class VolatileVisibilityExample {
// 不使用volatile,可能导致可见性问题
private static boolean flag = false;
// 使用volatile,保证可见性
private static volatile boolean volatileFlag = false;
public static void main(String[] args) throws InterruptedException {
// 示例1: 不使用volatile的问题
new Thread(() -> {
while (!flag) {
// 线程可能永远无法看到flag的变化
// JIT编译器可能将flag缓存到寄存器
}
System.out.println("线程1: flag变为true");
}).start();
Thread.sleep(1000);
flag = true; // 主线程修改flag
System.out.println("主线程: 已将flag设置为true");
// 示例2: 使用volatile保证可见性
new Thread(() -> {
while (!volatileFlag) {
// volatile保证能立即看到volatileFlag的变化
}
System.out.println("线程2: volatileFlag变为true");
}).start();
Thread.sleep(1000);
volatileFlag = true; // 主线程修改volatileFlag
System.out.println("主线程: 已将volatileFlag设置为true");
}
}
2. 有序性保证(禁止指令重排序)
volatile通过内存屏障(Memory Barrier)禁止特定类型的指令重排序。
内存屏障的四种类型:
| 屏障类型 | 指令示例 | 说明 |
|---|---|---|
| LoadLoad屏障 | Load1; LoadLoad; Load2 | 确保Load1的数据装载先于Load2及后续装载指令 |
| StoreStore屏障 | Store1; StoreStore; Store2 | 确保Store1的数据刷新到主内存先于Store2及后续存储指令 |
| LoadStore屏障 | Load1; LoadStore; Store2 | 确保Load1的数据装载先于Store2及后续存储指令 |
| StoreLoad屏障 | Store1; StoreLoad; Load2 | 确保Store1的数据刷新到主内存先于Load2及后续装载指令(开销最大) |
volatile写操作插入的内存屏障:
arduino
volatile写操作:
StoreStore屏障 ← 禁止上面的普通写与volatile写重排序
volatile write
StoreLoad屏障 ← 禁止volatile写与下面可能的volatile读/写重排序
volatile读操作插入的内存屏障:
arduino
volatile读操作:
volatile read
LoadLoad屏障 ← 禁止下面的普通读与volatile读重排序
LoadStore屏障 ← 禁止下面的普通写与volatile读重排序
经典案例:DCL双重检查锁定模式
java
public class Singleton {
// 必须使用volatile,防止指令重排序
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查(不加锁,提高性能)
synchronized (Singleton.class) { // 加锁
if (instance == null) { // 第二次检查(防止多次创建)
instance = new Singleton(); // ← 关键代码
}
}
}
return instance;
}
}
为什么必须使用volatile?
instance = new Singleton()实际包含三个步骤:
java
1. memory = allocate(); // 分配对象内存空间
2. ctorInstance(memory); // 初始化对象
3. instance = memory; // 设置instance指向刚分配的内存地址
由于指令重排序,可能变成:
java
1. memory = allocate(); // 分配对象内存空间
3. instance = memory; // 设置instance指向刚分配的内存地址(此时对象还未初始化!)
2. ctorInstance(memory); // 初始化对象
问题场景:
yaml
时间线:
T1: 线程A执行到步骤3,instance不为null,但对象还未初始化
T2: 线程B执行第一次检查,发现instance != null,直接返回instance
T3: 线程B使用instance访问对象属性,空指针异常或数据错误!
使用volatile禁止指令重排序后,保证步骤2一定在步骤3之前执行。
2.2.3 volatile vs synchronized对比
| 特性 | volatile | synchronized |
|---|---|---|
| 原子性 | ❌ 不保证(仅保证单次读/写的原子性) | ✅ 保证(临界区内的操作是原子的) |
| 可见性 | ✅ 保证 | ✅ 保证 |
| 有序性 | ✅ 保证(禁止指令重排序) | ✅ 保证(同一时刻只有一个线程执行) |
| 阻塞性 | ❌ 不阻塞 | ✅ 可能阻塞(重量级锁) |
| 适用范围 | 只能修饰变量 | 可以修饰方法、代码块 |
| 性能 | 高(只插入内存屏障) | 较低(可能涉及锁竞争和上下文切换) |
| 使用场景 | 状态标志、一次性安全发布 | 复合操作、临界区保护 |
使用场景选择:
java
// ✅ 适合使用volatile的场景
public class VolatileSuitableCase {
// 场景1: 状态标志
private volatile boolean shutdownRequested = false;
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// 执行任务
}
}
// 场景2: 一次性安全发布(配合DCL)
private volatile Singleton instance;
}
// ❌ 不适合使用volatile的场景(需要使用synchronized)
public class VolatileNotSuitableCase {
private volatile int count = 0;
// ❌ 错误:volatile不能保证count++的原子性
public void increment() {
count++; // 实际是三个操作: 读取 -> 加1 -> 写回
}
// ✅ 正确:使用synchronized保证原子性
public synchronized void incrementCorrect() {
count++;
}
}
2.3 线程间通信机制
2.3.1 wait/notify/notifyAll机制
核心方法:
Object.wait(): 释放当前持有的锁,进入对象的等待队列(WaitSet),线程状态变为WAITINGObject.wait(long timeout): 限时等待,超时后自动唤醒,线程状态变为TIMED_WAITINGObject.notify(): 随机唤醒等待队列中的一个线程,被唤醒的线程从WAITING状态转为BLOCKED状态,重新竞争锁Object.notifyAll(): 唤醒等待队列中的所有线程
为什么必须在synchronized块中使用?
- 防止丢失唤醒: 如果wait()不在synchronized块中,可能在调用wait()之前,notify()已经被调用,导致线程永久等待
- 保证原子性: wait/notify操作通常伴随着对共享变量的判断和修改,需要同步保护
java
// ❌ 错误示例:不在synchronized块中使用
public class WrongUsage {
private final Object lock = new Object();
public void wrongWait() throws InterruptedException {
lock.wait(); // IllegalMonitorStateException: 未持有锁
}
public void wrongNotify() {
lock.notify(); // IllegalMonitorStateException: 未持有锁
}
}
// ✅ 正确示例:在synchronized块中使用
public class CorrectUsage {
private final Object lock = new Object();
public void correctWait() throws InterruptedException {
synchronized (lock) {
lock.wait(); // 正确:持有锁
}
}
public void correctNotify() {
synchronized (lock) {
lock.notify(); // 正确:持有锁
}
}
}
wait的释放锁与阻塞过程:
scss
┌────────────────────────────────────────────────┐
│ wait()方法的执行流程 │
└────────────────────────────────────────────────┘
1. 线程持有synchronized锁
┌─────────────┐
│ Thread A │ ← 持有锁,_owner指向Thread A
└──────┬──────┘
│ 调用lock.wait()
▼
2. 释放锁,进入WaitSet
┌─────────────┐
│ _owner │ ← NULL(锁已释放)
└─────────────┘
┌─────────────┐
│ _WaitSet │ ← Thread A (WAITING状态)
└─────────────┘
┌─────────────┐
│ _EntryList │ ← Thread B (获得锁,继续执行)
└─────────────┘
│
│ 其他线程调用lock.notify()
▼
3. 从WaitSet移到EntryList,重新竞争锁
┌─────────────┐
│ _WaitSet │ ← 空
└─────────────┘
┌─────────────┐
│ _EntryList │ ← Thread A (BLOCKED状态,等待获取锁)
└─────────────┘
│
│ 获得锁
▼
4. 重新持有锁,继续执行wait()之后的代码
┌─────────────┐
│ _owner │ ← Thread A
└─────────────┘
notify的随机唤醒 vs notifyAll的全部唤醒:
java
public class NotifyExample {
private final Object lock = new Object();
public void testNotify() throws InterruptedException {
// 启动3个等待线程
for (int i = 1; i <= 3; i++) {
int threadNum = i;
new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程" + threadNum + "开始等待");
lock.wait();
System.out.println("线程" + threadNum + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(1000);
// 测试notify():只唤醒一个线程(随机)
synchronized (lock) {
System.out.println("调用notify(),只唤醒一个线程");
lock.notify(); // 只有一个线程被唤醒,其他两个继续等待
}
Thread.sleep(1000);
// 测试notifyAll():唤醒所有线程
synchronized (lock) {
System.out.println("调用notifyAll(),唤醒所有线程");
lock.notifyAll(); // 所有等待的线程被唤醒
}
}
}
虚假唤醒(Spurious Wakeup)问题:
在某些操作系统的实现中,wait()可能在没有被notify/notifyAll的情况下意外唤醒。因此,必须在循环中使用wait(),而不是if语句。
java
// ❌ 错误:使用if判断
public class SpuriousWakeupWrong {
private boolean condition = false;
private final Object lock = new Object();
public void waitForCondition() throws InterruptedException {
synchronized (lock) {
if (!condition) { // ❌ 使用if,虚假唤醒后不再检查条件
lock.wait();
}
// 执行业务逻辑
}
}
}
// ✅ 正确:使用while循环
public class SpuriousWakeupCorrect {
private boolean condition = false;
private final Object lock = new Object();
public void waitForCondition() throws InterruptedException {
synchronized (lock) {
while (!condition) { // ✅ 使用while,被唤醒后重新检查条件
lock.wait();
}
// 执行业务逻辑
}
}
public void signalCondition() {
synchronized (lock) {
condition = true;
lock.notifyAll();
}
}
}
经典案例:生产者-消费者模式
java
public class ProducerConsumer {
private final Queue<String> queue = new LinkedList<>();
private final int maxSize = 10;
private final Object lock = new Object();
// 生产者
public void produce(String item) throws InterruptedException {
synchronized (lock) {
// 使用while循环,防止虚假唤醒
while (queue.size() == maxSize) {
System.out.println("队列已满,生产者等待...");
lock.wait(); // 队列满,等待消费者消费
}
queue.offer(item);
System.out.println("生产: " + item + ", 当前队列大小: " + queue.size());
lock.notifyAll(); // 唤醒等待的消费者
}
}
// 消费者
public String consume() throws InterruptedException {
synchronized (lock) {
// 使用while循环,防止虚假唤醒
while (queue.isEmpty()) {
System.out.println("队列为空,消费者等待...");
lock.wait(); // 队列空,等待生产者生产
}
String item = queue.poll();
System.out.println("消费: " + item + ", 当前队列大小: " + queue.size());
lock.notifyAll(); // 唤醒等待的生产者
return item;
}
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
// 启动3个生产者
for (int i = 1; i <= 3; i++) {
int producerId = i;
new Thread(() -> {
try {
for (int j = 1; j <= 5; j++) {
pc.produce("产品-" + producerId + "-" + j);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者-" + i).start();
}
// 启动2个消费者
for (int i = 1; i <= 2; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 7; j++) {
pc.consume();
Thread.sleep(150);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者-" + i).start();
}
}
}
2.3.2 LockSupport工具类
LockSupport是JDK 1.5引入的线程阻塞工具类,提供了更灵活的线程阻塞和唤醒机制。
核心方法:
LockSupport.park(): 阻塞当前线程,除非许可(permit)可用LockSupport.park(Object blocker): 阻塞当前线程,并设置阻塞对象(便于诊断)LockSupport.parkNanos(long nanos): 限时阻塞(纳秒级)LockSupport.unpark(Thread thread): 使指定线程可用许可,如果线程被阻塞,则唤醒它
许可(Permit)机制:
LockSupport内部维护了一个许可(permit),类似于信号量,但最多只有一个许可(0或1)。
css
许可机制:
1. unpark() → 许可设置为1
2. park() → 如果许可为1,消耗许可(设置为0),继续执行
如果许可为0,阻塞线程
特点:
- 许可最多为1,多次unpark()不会累加
- unpark()可以在park()之前调用(与wait/notify不同)
与wait/notify的区别:
| 特性 | wait/notify | LockSupport.park/unpark |
|---|---|---|
| 是否需要synchronized | ✅ 必须在synchronized块中 | ❌ 不需要 |
| 唤醒指定线程 | ❌ notify()随机唤醒 | ✅ unpark(thread)指定线程 |
| 顺序要求 | ✅ 必须先wait()后notify() | ❌ unpark()可以在park()之前调用 |
| 许可累加 | ❌ 不支持 | ❌ 不支持(最多1个许可) |
| 响应中断 | ✅ 抛出InterruptedException | ✅ 不抛异常,但Thread.interrupted()返回true |
基本使用示例:
java
public class LockSupportExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("线程开始,准备阻塞");
LockSupport.park(); // 阻塞,等待许可
System.out.println("线程被唤醒,继续执行");
});
thread.start();
Thread.sleep(2000);
System.out.println("主线程唤醒子线程");
LockSupport.unpark(thread); // 给thread一个许可,唤醒线程
}
}
许可机制演示:
java
public class LockSupportPermitExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("线程启动");
// 场景1: unpark()在park()之前调用
// 由于已经有许可,park()不会阻塞
LockSupport.park();
System.out.println("第一次park()不阻塞(许可已提前发放)");
// 场景2: 许可已被消耗,再次park()会阻塞
System.out.println("第二次park()会阻塞(许可已消耗)");
LockSupport.park();
System.out.println("第二次park()被唤醒");
});
// 提前发放许可
LockSupport.unpark(thread);
thread.start();
Thread.sleep(2000);
// 唤醒第二次park()
LockSupport.unpark(thread);
}
}
LockSupport的应用场景:
- 实现自定义锁: AQS(AbstractQueuedSynchronizer)底层使用LockSupport实现线程阻塞和唤醒
- 精确唤醒指定线程: 不需要notifyAll()唤醒所有线程,减少无效唤醒
- 灵活的阻塞机制: 不需要获取锁,使用更灵活
三、实战场景应用
场景1:电商订单状态更新的并发控制
业务背景:
在电商系统中,订单状态可能被多个线程同时修改:
- 用户支付成功后,支付回调线程将订单状态从"待支付"改为"待发货"
- 订单超时未支付,定时任务线程将订单状态改为"已取消"
- 管理员手动取消订单,管理后台线程将订单状态改为"已取消"
问题分析:
如果不进行并发控制,可能出现以下问题:
- 竞态条件: 支付回调和超时取消同时执行,导致状态不一致
- 丢失更新: 后执行的操作覆盖先执行的操作
- 状态机混乱: 订单状态跳过中间状态,如从"待支付"直接变为"已完成"
java
// 订单状态枚举
public enum OrderStatus {
PENDING_PAYMENT(1, "待支付"),
PAID(2, "已支付"),
SHIPPED(3, "已发货"),
COMPLETED(4, "已完成"),
CANCELLED(5, "已取消");
private final int code;
private final String desc;
OrderStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
}
// 订单实体
public class Order {
private Long orderId;
private OrderStatus status;
private Integer version; // 乐观锁版本号
private Long userId;
private BigDecimal amount;
private LocalDateTime createTime;
private LocalDateTime updateTime;
// getter/setter省略
}
解决方案A:synchronized方法控制
java
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
@Autowired
private OrderMapper orderMapper;
// 方案A: 使用synchronized控制并发
public synchronized boolean updateOrderStatus(Long orderId, OrderStatus targetStatus, String operator) {
// 1. 查询订单
Order order = orderMapper.selectById(orderId);
if (order == null) {
log.warn("订单不存在, orderId={}", orderId);
return false;
}
// 2. 状态机校验
if (!validateStatusTransition(order.getStatus(), targetStatus)) {
log.warn("订单状态转换不合法, orderId={}, currentStatus={}, targetStatus={}",
orderId, order.getStatus(), targetStatus);
return false;
}
// 3. 更新订单状态
order.setStatus(targetStatus);
order.setUpdateTime(LocalDateTime.now());
orderMapper.updateById(order);
log.info("订单状态更新成功, orderId={}, {} -> {}, operator={}",
orderId, order.getStatus(), targetStatus, operator);
return true;
}
// 状态机校验:定义合法的状态转换
private boolean validateStatusTransition(OrderStatus current, OrderStatus target) {
if (current == target) {
return false; // 相同状态,无需更新
}
switch (current) {
case PENDING_PAYMENT:
// 待支付 → 已支付、已取消
return target == OrderStatus.PAID || target == OrderStatus.CANCELLED;
case PAID:
// 已支付 → 已发货、已取消(特殊情况)
return target == OrderStatus.SHIPPED || target == OrderStatus.CANCELLED;
case SHIPPED:
// 已发货 → 已完成
return target == OrderStatus.COMPLETED;
case COMPLETED:
case CANCELLED:
// 已完成、已取消为终态,不允许再转换
return false;
default:
return false;
}
}
}
优点:
- 实现简单,代码清晰
- 保证同一时刻只有一个线程修改订单状态
缺点:
- 锁粒度过大,synchronized锁住整个方法,所有订单更新都串行执行
- 性能差,高并发场景下成为瓶颈
- 单机锁,分布式环境下无法保证安全性
解决方案B:数据库乐观锁(version字段)
java
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
@Autowired
private OrderMapper orderMapper;
// 方案B: 使用数据库乐观锁
public boolean updateOrderStatusWithOptimisticLock(Long orderId, OrderStatus targetStatus, String operator) {
// 最多重试3次
int maxRetry = 3;
for (int i = 0; i < maxRetry; i++) {
// 1. 查询订单(包含version)
Order order = orderMapper.selectById(orderId);
if (order == null) {
log.warn("订单不存在, orderId={}", orderId);
return false;
}
// 2. 状态机校验
if (!validateStatusTransition(order.getStatus(), targetStatus)) {
log.warn("订单状态转换不合法, orderId={}, currentStatus={}, targetStatus={}",
orderId, order.getStatus(), targetStatus);
return false;
}
// 3. 使用乐观锁更新(WHERE id=? AND version=?)
Integer oldVersion = order.getVersion();
order.setStatus(targetStatus);
order.setVersion(oldVersion + 1);
order.setUpdateTime(LocalDateTime.now());
int affectedRows = orderMapper.updateByIdAndVersion(order, oldVersion);
if (affectedRows > 0) {
log.info("订单状态更新成功, orderId={}, {} -> {}, operator={}, retry={}",
orderId, order.getStatus(), targetStatus, operator, i);
return true;
} else {
log.warn("订单状态更新失败(版本冲突), orderId={}, 第{}次重试", orderId, i + 1);
// 版本冲突,重试
try {
Thread.sleep(50); // 避免频繁重试
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
log.error("订单状态更新失败(超过最大重试次数), orderId={}", orderId);
return false;
}
private boolean validateStatusTransition(OrderStatus current, OrderStatus target) {
// 同方案A
// ...
}
}
// MyBatis Mapper
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
/**
* 乐观锁更新订单
* SQL: UPDATE t_order SET status=#{status}, version=#{version}, update_time=#{updateTime}
* WHERE id=#{id} AND version=#{oldVersion}
*/
@Update("UPDATE t_order SET status=#{order.status}, version=#{order.version}, " +
"update_time=#{order.updateTime} " +
"WHERE id=#{order.id} AND version=#{oldVersion}")
int updateByIdAndVersion(@Param("order") Order order, @Param("oldVersion") Integer oldVersion);
}
优点:
- 无锁化设计,高并发性能好
- 通过version字段保证更新的原子性
- 适用于读多写少的场景
缺点:
- 冲突时需要重试,增加复杂度
- 重试次数过多可能影响性能
- 单表更新有效,涉及多表事务时需要额外处理
解决方案C:分布式锁
java
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
@Autowired
private OrderMapper orderMapper;
@Autowired
private RedissonClient redissonClient;
// 方案C: 使用分布式锁(Redisson)
public boolean updateOrderStatusWithDistributedLock(Long orderId, OrderStatus targetStatus, String operator) {
String lockKey = "order:lock:" + orderId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,最多等待10秒,锁自动释放时间30秒
boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (!locked) {
log.warn("获取分布式锁失败, orderId={}", orderId);
return false;
}
// 1. 查询订单
Order order = orderMapper.selectById(orderId);
if (order == null) {
log.warn("订单不存在, orderId={}", orderId);
return false;
}
// 2. 状态机校验
if (!validateStatusTransition(order.getStatus(), targetStatus)) {
log.warn("订单状态转换不合法, orderId={}, currentStatus={}, targetStatus={}",
orderId, order.getStatus(), targetStatus);
return false;
}
// 3. 更新订单状态
order.setStatus(targetStatus);
order.setUpdateTime(LocalDateTime.now());
orderMapper.updateById(order);
log.info("订单状态更新成功, orderId={}, {} -> {}, operator={}",
orderId, order.getStatus(), targetStatus, operator);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取分布式锁被中断, orderId={}", orderId, e);
return false;
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private boolean validateStatusTransition(OrderStatus current, OrderStatus target) {
// 同方案A
// ...
}
}
优点:
- 分布式环境下保证安全性
- 锁粒度可控(按订单ID加锁,不同订单并行处理)
- 支持锁续期(Redisson Watchdog机制)
缺点:
- 依赖Redis,增加系统复杂度
- 网络延迟影响性能
- 需要处理Redis故障和锁续期问题
性能测试对比:
| 方案 | 并发线程数 | 总请求数 | 平均RT | TPS | 成功率 |
|---|---|---|---|---|---|
| synchronized方法 | 100 | 10000 | 250ms | 400 | 100% |
| 数据库乐观锁 | 100 | 10000 | 80ms | 1250 | 95%(重试后成功) |
| 分布式锁(Redisson) | 100 | 10000 | 120ms | 833 | 100% |
推荐方案:
- 单机应用: 使用数据库乐观锁(方案B),性能最优
- 分布式应用: 使用分布式锁(方案C),保证安全性
- 低并发场景: 使用synchronized(方案A),实现简单
场景2:使用volatile实现优雅停止线程
业务背景:
监控线程周期性检查系统状态(如订单超时检测、库存同步),需要能优雅停止线程而不是强制中断。
错误实现:boolean变量未加volatile
java
public class StopThreadWrong {
// ❌ 错误:未使用volatile,可能导致主线程修改后,工作线程无法感知
private boolean stopRequested = false;
public void start() {
// 启动工作线程
new Thread(() -> {
int count = 0;
while (!stopRequested) { // 可能永远读取不到stopRequested的变化
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("工作线程停止, 总执行次数: " + count);
}, "Worker-Thread").start();
}
public void stop() {
stopRequested = true; // 主线程修改,工作线程可能看不到
System.out.println("主线程已设置停止标志");
}
public static void main(String[] args) throws InterruptedException {
StopThreadWrong worker = new StopThreadWrong();
worker.start();
Thread.sleep(5000);
worker.stop(); // 工作线程可能无法停止!
}
}
问题原因:
arduino
内存可见性问题:
主线程(CPU0) 工作线程(CPU1)
┌─────────────┐ ┌─────────────┐
│ stopRequested│ │ stopRequested│
│ = false │ │ = false │ ← CPU1缓存中的值
└──────┬──────┘ └──────┬──────┘
│ │
│ stopRequested = true │ while(!stopRequested)
▼ │ 循环继续...
┌─────────────┐ │
│主内存 │ │
│stopRequested│ │
│ = true │ ▼
└─────────────┘ 可能永远读取不到true!
(CPU1缓存未失效,一直使用缓存值false)
正确实现:使用volatile
java
public class StopThreadCorrect {
// ✅ 正确:使用volatile,保证可见性
private volatile boolean stopRequested = false;
public void start() {
// 启动工作线程
new Thread(() -> {
int count = 0;
while (!stopRequested) { // volatile保证能立即读取到主线程的修改
count++;
System.out.println("执行任务 #" + count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println("工作线程优雅停止, 总执行次数: " + count);
}, "Worker-Thread").start();
}
public void stop() {
stopRequested = true; // volatile写操作,立即刷新到主内存
System.out.println("主线程已设置停止标志");
}
public static void main(String[] args) throws InterruptedException {
StopThreadCorrect worker = new StopThreadCorrect();
worker.start();
Thread.sleep(5000);
worker.stop(); // 工作线程能立即感知到stopRequested变化,优雅停止
}
}
原理解释:
arduino
volatile保证可见性:
主线程 工作线程
│ │
│ stopRequested = true │
│ ↓ │
│ StoreStore屏障 │
│ ↓ │
│ 写入主内存 │
│ ↓ │
│ StoreLoad屏障 │
│ ↓ │
└──────────────────────────────┤
│
│ while(!stopRequested)
│ ↓
│ LoadLoad屏障
│ ↓
│ 从主内存读取(强制刷新CPU缓存)
│ ↓
│ 读取到true,退出循环
实际应用:订单超时检测
java
@Component
public class OrderTimeoutMonitor {
private static final Logger log = LoggerFactory.getLogger(OrderTimeoutMonitor.class);
@Autowired
private OrderService orderService;
// 停止标志
private volatile boolean stopRequested = false;
// 监控线程
private Thread monitorThread;
/**
* 启动监控
*/
public void start() {
if (monitorThread != null && monitorThread.isAlive()) {
log.warn("监控线程已启动,无需重复启动");
return;
}
stopRequested = false;
monitorThread = new Thread(() -> {
log.info("订单超时监控线程启动");
while (!stopRequested) {
try {
// 查询超时未支付的订单
List<Order> timeoutOrders = orderService.selectTimeoutOrders();
for (Order order : timeoutOrders) {
// 自动取消超时订单
boolean success = orderService.updateOrderStatus(
order.getOrderId(),
OrderStatus.CANCELLED,
"System-Timeout"
);
if (success) {
log.info("自动取消超时订单, orderId={}", order.getOrderId());
}
}
// 每60秒检查一次
Thread.sleep(60000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("监控线程被中断");
break;
} catch (Exception e) {
log.error("监控线程异常", e);
}
}
log.info("订单超时监控线程停止");
}, "Order-Timeout-Monitor");
monitorThread.start();
}
/**
* 停止监控
*/
public void stop() {
stopRequested = true; // volatile写,保证可见性
if (monitorThread != null) {
monitorThread.interrupt(); // 中断sleep状态
}
log.info("已发送停止信号");
}
/**
* 应用启动时自动启动监控
*/
@PostConstruct
public void init() {
start();
}
/**
* 应用关闭时优雅停止监控
*/
@PreDestroy
public void destroy() {
stop();
}
}
四、生产案例与故障排查
案例1:线程死锁排查与解决
故障现象:
某电商平台在促销活动期间,系统响应缓慢,部分用户下单接口卡死超时。监控显示CPU使用率不高,但TPS大幅下降。
排查过程:
步骤1:使用jstack生成线程dump
bash
# 查找Java进程PID
jps -l
# 生成线程dump
jstack -l 12345 > thread_dump.txt
步骤2:分析死锁日志
在thread_dump.txt中发现死锁信息:
vbnet
Found one Java-level deadlock:
=============================
"Thread-A":
waiting to lock monitor 0x00007f8a1c004e00 (object 0x00000000e0a12345, a com.example.OrderLock),
which is held by "Thread-B"
"Thread-B":
waiting to lock monitor 0x00007f8a1c004d00 (object 0x00000000e0a12346, a com.example.InventoryLock),
which is held by "Thread-A"
Java stack information for the threads listed above:
===================================================
"Thread-A":
at com.example.OrderService.createOrder(OrderService.java:45)
- waiting to lock <0x00000000e0a12345> (a com.example.OrderLock)
- locked <0x00000000e0a12346> (a com.example.InventoryLock)
"Thread-B":
at com.example.InventoryService.deductStock(InventoryService.java:32)
- waiting to lock <0x00000000e0a12346> (a com.example.InventoryLock)
- locked <0x00000000e0a12345> (a com.example.OrderLock)
步骤3:定位死锁代码
java
// ❌ 死锁代码(错误示例)
@Service
public class OrderService {
private final Object orderLock = new Object();
private final Object inventoryLock = new Object();
@Autowired
private InventoryService inventoryService;
// 线程A:先锁库存,再锁订单
public void createOrder(Long skuId, int quantity) {
synchronized (inventoryLock) { // ← 先获取inventoryLock
System.out.println("OrderService获取inventoryLock");
// 模拟业务处理
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (orderLock) { // ← 再获取orderLock
System.out.println("OrderService获取orderLock");
// 创建订单逻辑
}
}
}
}
@Service
public class InventoryService {
private final Object orderLock = new Object();
private final Object inventoryLock = new Object();
// 线程B:先锁订单,再锁库存
public void deductStock(Long orderId, Long skuId, int quantity) {
synchronized (orderLock) { // ← 先获取orderLock
System.out.println("InventoryService获取orderLock");
// 模拟业务处理
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (inventoryLock) { // ← 再获取inventoryLock
System.out.println("InventoryService获取inventoryLock");
// 扣减库存逻辑
}
}
}
}
死锁场景复现:
less
时间线:
T1: 线程A执行createOrder(),获取inventoryLock
T2: 线程B执行deductStock(),获取orderLock
T3: 线程A尝试获取orderLock,被线程B持有,阻塞等待
T4: 线程B尝试获取inventoryLock,被线程A持有,阻塞等待
T5: 死锁!两个线程互相等待对方释放锁
┌─────────────────────────────────────────────────┐
│ 死锁示意图 │
└─────────────────────────────────────────────────┘
Thread-A Thread-B
│ │
│ 获取inventoryLock │
├────────────────► │
│ 持有inventoryLock │
│ │ 获取orderLock
│ ├────────────────►
│ │ 持有orderLock
│ │
│ 尝试获取orderLock │
├─────────┐ │
│ 等待 │ ◄──────────────────┤ 被Thread-B持有
│ │ │
│ │ │ 尝试获取inventoryLock
│ │ ├─────────┐
│ │ 被Thread-A持有 ───►│ 等待 │
│ │ │ │
│ │ │ │
└─────────┴────────────────────┴─────────┘
死锁!
死锁的四个必要条件:
- 互斥条件: 资源不能被多个线程同时访问
- 持有并等待: 线程持有一个资源,同时等待获取另一个资源
- 不可剥夺: 资源不能被强制从持有线程中剥夺
- 循环等待: 存在线程等待环路(A等B,B等A)
解决方案1:统一锁顺序(破坏循环等待)
java
// ✅ 解决方案1:统一锁的获取顺序
@Service
public class OrderService {
// 使用同一把锁对象(从Spring容器获取)
@Autowired
private LockManager lockManager;
public void createOrder(Long skuId, int quantity) {
Object inventoryLock = lockManager.getInventoryLock();
Object orderLock = lockManager.getOrderLock();
// 统一按照:先锁inventory,再锁order
synchronized (inventoryLock) {
System.out.println("OrderService获取inventoryLock");
synchronized (orderLock) {
System.out.println("OrderService获取orderLock");
// 创建订单逻辑
}
}
}
}
@Service
public class InventoryService {
@Autowired
private LockManager lockManager;
public void deductStock(Long orderId, Long skuId, int quantity) {
Object inventoryLock = lockManager.getInventoryLock();
Object orderLock = lockManager.getOrderLock();
// 统一按照:先锁inventory,再锁order(与OrderService保持一致)
synchronized (inventoryLock) {
System.out.println("InventoryService获取inventoryLock");
synchronized (orderLock) {
System.out.println("InventoryService获取orderLock");
// 扣减库存逻辑
}
}
}
}
@Component
public class LockManager {
private final Object inventoryLock = new Object();
private final Object orderLock = new Object();
public Object getInventoryLock() {
return inventoryLock;
}
public Object getOrderLock() {
return orderLock;
}
}
解决方案2:使用tryLock超时机制(破坏持有并等待)
java
// ✅ 解决方案2:使用Lock的tryLock()方法
@Service
public class OrderService {
private final Lock inventoryLock = new ReentrantLock();
private final Lock orderLock = new ReentrantLock();
public boolean createOrder(Long skuId, int quantity) {
try {
// 尝试获取inventoryLock,最多等待1秒
if (inventoryLock.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("OrderService获取inventoryLock");
// 尝试获取orderLock,最多等待1秒
if (orderLock.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println("OrderService获取orderLock");
// 创建订单逻辑
return true;
} finally {
orderLock.unlock();
}
} else {
System.out.println("OrderService获取orderLock超时,放弃操作");
return false;
}
} finally {
inventoryLock.unlock();
}
} else {
System.out.println("OrderService获取inventoryLock超时,放弃操作");
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
解决方案3:重构代码,避免嵌套锁
java
// ✅ 解决方案3:避免嵌套锁,使用单锁或无锁设计
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryService inventoryService;
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long skuId, int quantity) {
// 1. 先扣减库存(原子操作,数据库层面保证)
boolean deducted = inventoryService.tryDeductStock(skuId, quantity);
if (!deducted) {
throw new BusinessException("库存不足");
}
// 2. 创建订单(无需额外加锁)
Order order = new Order();
order.setSkuId(skuId);
order.setQuantity(quantity);
order.setStatus(OrderStatus.PENDING_PAYMENT);
orderMapper.insert(order);
}
}
@Service
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
// 使用数据库CAS操作,避免加锁
public boolean tryDeductStock(Long skuId, int quantity) {
// UPDATE inventory SET stock = stock - #{quantity}
// WHERE sku_id = #{skuId} AND stock >= #{quantity}
int affectedRows = inventoryMapper.deductStock(skuId, quantity);
return affectedRows > 0;
}
}
预防死锁的最佳实践:
- 避免嵌套锁: 尽量使用单锁,减少锁的嵌套层级
- 统一锁顺序: 如果必须嵌套,所有线程按相同顺序获取锁
- 使用tryLock: 设置超时时间,避免无限等待
- 缩小锁范围: 只锁必要的代码块,减少持锁时间
- 使用并发工具类: 如ConcurrentHashMap、原子类,避免显式加锁
案例2:synchronized锁粒度过大导致性能问题
故障现象:
某订单查询接口在高并发下TPS严重下降,从正常的1000 TPS降至200 TPS,平均响应时间从50ms上升至300ms。
问题分析:
java
// ❌ 问题代码:整个方法加锁,锁粒度过大
@Service
public class OrderQueryService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 问题:synchronized锁住整个方法,包括数据库查询和缓存操作
public synchronized OrderDTO getOrderDetail(Long orderId) {
// 1. 查询缓存(约50ms)
String cacheKey = "order:" + orderId;
OrderDTO cached = (OrderDTO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 2. 查询数据库(约100ms)
Order order = orderMapper.selectById(orderId);
if (order == null) {
return null;
}
// 3. 转换DTO(约10ms)
OrderDTO dto = convertToDTO(order);
// 4. 写入缓存(约50ms)
redisTemplate.opsForValue().set(cacheKey, dto, 10, TimeUnit.MINUTES);
return dto;
}
private OrderDTO convertToDTO(Order order) {
// 转换逻辑
return new OrderDTO();
}
}
性能瓶颈分析:
makefile
高并发场景(100个线程同时查询):
┌─────────────────────────────────────────────────┐
│ synchronized方法串行执行 │
└─────────────────────────────────────────────────┘
Thread-1: [查缓存|查DB|转换|写缓存] ───► 210ms
│
Thread-2: [查缓存|查DB|转换|写缓存] ───► 420ms (等待210ms)
│
Thread-3: [查缓存|查DB|转换|写缓存] ───► 630ms (等待420ms)
│
Thread-100: ... ───► 21000ms (等待20790ms)
平均响应时间: 10500ms (完全不可接受!)
TPS: 100 / 21s ≈ 4.76 (严重下降)
优化方案1:缩小synchronized块范围
java
// ✅ 优化方案1:缩小synchronized块,只保护关键代码
@Service
public class OrderQueryService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 每个订单一个锁对象,避免全局锁
private final ConcurrentHashMap<Long, Object> locks = new ConcurrentHashMap<>();
public OrderDTO getOrderDetail(Long orderId) {
// 1. 查询缓存(不加锁)
String cacheKey = "order:" + orderId;
OrderDTO cached = (OrderDTO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 2. 缓存未命中,加锁查询数据库(防止缓存击穿)
Object lock = locks.computeIfAbsent(orderId, k -> new Object());
synchronized (lock) {
// 双重检查:再次查询缓存
cached = (OrderDTO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 查询数据库
Order order = orderMapper.selectById(orderId);
if (order == null) {
return null;
}
// 转换DTO
OrderDTO dto = convertToDTO(order);
// 写入缓存
redisTemplate.opsForValue().set(cacheKey, dto, 10, TimeUnit.MINUTES);
return dto;
}
// 注意:这里可以考虑清理locks中的对象,避免内存泄漏
}
private OrderDTO convertToDTO(Order order) {
return new OrderDTO();
}
}
优化方案2:读写分离锁(ReadWriteLock)
java
// ✅ 优化方案2:使用读写锁,允许多个读线程并发
@Service
public class OrderQueryService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 读写锁:多个读线程可以并发,写线程独占
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public OrderDTO getOrderDetail(Long orderId) {
String cacheKey = "order:" + orderId;
// 1. 读锁:查询缓存(允许多线程并发读)
readLock.lock();
try {
OrderDTO cached = (OrderDTO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
} finally {
readLock.unlock();
}
// 2. 写锁:查询数据库并写入缓存(独占)
writeLock.lock();
try {
// 双重检查
OrderDTO cached = (OrderDTO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
Order order = orderMapper.selectById(orderId);
if (order == null) {
return null;
}
OrderDTO dto = convertToDTO(order);
redisTemplate.opsForValue().set(cacheKey, dto, 10, TimeUnit.MINUTES);
return dto;
} finally {
writeLock.unlock();
}
}
private OrderDTO convertToDTO(Order order) {
return new OrderDTO();
}
}
优化方案3:无锁化设计(推荐)
java
// ✅ 优化方案3:使用Guava LoadingCache,自动加载,无需手动加锁
@Service
public class OrderQueryService {
@Autowired
private OrderMapper orderMapper;
// Guava Cache:自动加载,线程安全,无需手动加锁
private final LoadingCache<Long, OrderDTO> orderCache = CacheBuilder.newBuilder()
.maximumSize(10000) // 最多缓存10000个订单
.expireAfterWrite(10, TimeUnit.MINUTES) // 10分钟过期
.build(new CacheLoader<Long, OrderDTO>() {
@Override
public OrderDTO load(Long orderId) throws Exception {
// 缓存未命中时自动调用此方法
Order order = orderMapper.selectById(orderId);
if (order == null) {
throw new OrderNotFoundException("订单不存在: " + orderId);
}
return convertToDTO(order);
}
});
public OrderDTO getOrderDetail(Long orderId) {
try {
// 自动处理并发加载,无需手动加锁
return orderCache.get(orderId);
} catch (ExecutionException e) {
if (e.getCause() instanceof OrderNotFoundException) {
return null;
}
throw new RuntimeException("查询订单失败", e);
}
}
private OrderDTO convertToDTO(Order order) {
return new OrderDTO();
}
}
性能提升对比:
| 方案 | 并发线程数 | 平均RT | TPS | 提升幅度 |
|---|---|---|---|---|
| synchronized方法(原始) | 100 | 300ms | 200 | - |
| 缩小synchronized块 | 100 | 80ms | 800 | 4倍 |
| ReadWriteLock | 100 | 60ms | 1000 | 5倍 |
| 无锁化设计(Guava Cache) | 100 | 50ms | 1200 | 6倍 |
五、常见问题与避坑指南
5.1 为什么wait/notify必须在synchronized块中?
原因1:防止丢失唤醒(Lost Wakeup)
java
// ❌ 错误示例:不在synchronized块中使用
public class LostWakeup {
private boolean condition = false;
private final Object lock = new Object();
// 线程A:等待条件
public void waitForCondition() throws InterruptedException {
if (!condition) { // ← 检查条件(未加锁)
// 假设此时线程切换,线程B执行notifyCondition()
lock.wait(); // ← 可能永久阻塞(notify已经被调用)
}
}
// 线程B:唤醒线程
public void notifyCondition() {
condition = true;
lock.notify(); // ← notify在wait()之前被调用,丢失唤醒
}
}
时间线分析:
scss
时间 | 线程A | 线程B
-----|-------|-------
T1 | if (!condition) // false,准备wait() |
T2 | | condition = true; notify(); ← notify先执行
T3 | lock.wait(); ← 永久阻塞(notify已丢失) |
原因2:保证原子性
wait/notify通常伴随共享变量的判断和修改,需要同步保护。
java
// ✅ 正确示例:在synchronized块中使用
public class CorrectWaitNotify {
private boolean condition = false;
private final Object lock = new Object();
public void waitForCondition() throws InterruptedException {
synchronized (lock) { // ← 先获取锁
while (!condition) { // ← 原子性检查条件
lock.wait(); // ← 释放锁,等待通知
}
// 被唤醒后,重新持有锁,继续执行
}
}
public void notifyCondition() {
synchronized (lock) { // ← 先获取锁
condition = true; // ← 原子性修改条件
lock.notifyAll(); // ← 唤醒等待线程
}
}
}
5.2 synchronized锁的是什么?
java
public class SynchronizedTarget {
private final Object lock = new Object();
private static final Object staticLock = new Object();
// 1. synchronized修饰普通方法:锁的是this对象
public synchronized void method1() {
// 等价于: synchronized (this) { ... }
}
// 2. synchronized修饰静态方法:锁的是Class对象
public static synchronized void method2() {
// 等价于: synchronized (SynchronizedTarget.class) { ... }
}
// 3. synchronized(this):锁的是当前实例对象
public void method3() {
synchronized (this) {
// 与method1()竞争同一把锁
}
}
// 4. synchronized(lock):锁的是lock对象
public void method4() {
synchronized (lock) {
// 独立的锁,不与其他方法竞争
}
}
// 5. synchronized(Class对象):锁的是类锁
public void method5() {
synchronized (SynchronizedTarget.class) {
// 与method2()竞争同一把锁
}
}
}
注意事项:
- 不同实例的synchronized方法不互斥:每个实例有自己的锁(this)
- 静态synchronized方法与普通synchronized方法不互斥:锁的是不同对象(Class vs this)
- String、Integer等常量池对象不要作为锁:可能被多个地方共享,导致意外的锁竞争
java
// ❌ 错误:使用String作为锁
public class StringLockProblem {
private String lock = "LOCK"; // ← 字符串常量,可能被其他代码共享
public void method() {
synchronized (lock) { // ← 可能与其他使用"LOCK"字符串的代码竞争同一把锁
// ...
}
}
}
// ✅ 正确:使用new Object()作为锁
public class ObjectLockCorrect {
private final Object lock = new Object(); // ← 独立的锁对象
public void method() {
synchronized (lock) {
// ...
}
}
}
5.3 volatile能保证原子性吗?为什么i++不安全?
答案:volatile不能保证原子性。
java
public class VolatileNotAtomic {
private volatile int count = 0;
public void increment() {
count++; // ❌ 不是原子操作!
}
}
count++的字节码分析:
arduino
count++实际分为3个步骤:
1. LOAD: 从主内存读取count的值到工作内存 (读操作)
2. ADD: 工作内存中count值+1 (计算操作)
3. STORE: 将新值写回主内存 (写操作)
字节码:
getstatic count // 读取count
iconst_1 // 加载常量1
iadd // 相加
putstatic count // 写回count
并发问题场景:
ini
初始: count = 0
时间线:
T1: 线程A读取count=0 (LOAD)
T2: 线程B读取count=0 (LOAD)
T3: 线程A计算0+1=1 (ADD)
T4: 线程B计算0+1=1 (ADD)
T5: 线程A写回count=1 (STORE)
T6: 线程B写回count=1 (STORE) ← 覆盖了线程A的结果
预期结果: count = 2
实际结果: count = 1 ← 丢失了一次更新!
volatile只保证了每次读写的可见性,但无法保证多步操作的原子性。
正确做法:
java
// 方案1:使用synchronized
public synchronized void increment() {
count++;
}
// 方案2:使用AtomicInteger
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // CAS原子操作
}
5.4 什么时候用synchronized,什么时候用volatile?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单状态标志 | volatile | 只需要可见性,无复合操作 |
| 一次性安全发布(DCL单例) | volatile | 防止指令重排序 |
| 计数器(i++) | synchronized / AtomicInteger | 需要原子性 |
| 复合操作(check-then-act) | synchronized | 需要原子性和可见性 |
| 临界区保护 | synchronized | 需要互斥访问 |
| 长时间持锁 | Lock | 支持超时、中断、公平锁 |
java
// ✅ 适合volatile的场景
public class VolatileSuitable {
private volatile boolean shutdownRequested = false;
public void shutdown() {
shutdownRequested = true; // 简单赋值,无复合操作
}
public void doWork() {
while (!shutdownRequested) { // 只读取,不修改
// ...
}
}
}
// ❌ 不适合volatile的场景
public class VolatileNotSuitable {
private volatile int count = 0;
public void increment() {
count++; // ❌ 复合操作,volatile无法保证原子性
}
// ✅ 应该使用synchronized
public synchronized void incrementCorrect() {
count++;
}
}
5.5 Thread.sleep()和Object.wait()的区别
| 特性 | Thread.sleep() | Object.wait() |
|---|---|---|
| 所属类 | Thread类(静态方法) | Object类(实例方法) |
| 是否释放锁 | ❌ 不释放 | ✅ 释放 |
| 是否需要在synchronized中 | ❌ 不需要 | ✅ 必须 |
| 唤醒方式 | 时间到自动唤醒 | notify/notifyAll唤醒或超时 |
| 使用场景 | 暂停执行一段时间 | 线程间协作,等待条件满足 |
java
public class SleepVsWait {
private final Object lock = new Object();
public void testSleep() throws InterruptedException {
synchronized (lock) {
System.out.println("持有锁,准备sleep");
Thread.sleep(2000); // ← 不释放锁,持有2秒
System.out.println("sleep结束,仍持有锁");
}
}
public void testWait() throws InterruptedException {
synchronized (lock) {
System.out.println("持有锁,准备wait");
lock.wait(); // ← 释放锁,等待被唤醒
System.out.println("被唤醒,重新获得锁");
}
}
}
5.6 如何优雅地停止一个线程?
❌ 错误方式:使用stop()方法(已废弃)
java
Thread thread = new Thread(() -> {
// ...
});
thread.start();
thread.stop(); // ❌ 已废弃,不安全!会导致数据不一致
✅ 正确方式1:使用volatile标志位
java
public class StopThreadWithFlag {
private volatile boolean stopRequested = false;
public void start() {
new Thread(() -> {
while (!stopRequested) {
// 执行任务
}
System.out.println("线程优雅停止");
}).start();
}
public void stop() {
stopRequested = true;
}
}
✅ 正确方式2:使用interrupt()中断
java
public class StopThreadWithInterrupt {
private Thread thread;
public void start() {
thread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
Thread.sleep(1000); // 响应中断
}
} catch (InterruptedException e) {
// 捕获中断异常,执行清理工作
System.out.println("线程被中断");
}
System.out.println("线程优雅停止");
});
thread.start();
}
public void stop() {
if (thread != null) {
thread.interrupt(); // 发送中断信号
}
}
}
5.7 synchronized方法和synchronized(this)的区别?
答案:没有本质区别,synchronized方法是synchronized(this)的语法糖。
java
public class SynchronizedMethodVsBlock {
// 方式1:synchronized方法
public synchronized void method1() {
// ...
}
// 方式2:synchronized(this)代码块
public void method2() {
synchronized (this) {
// ...
}
}
// 两者等价,锁的都是this对象
}
区别:
- 锁粒度: synchronized方法锁整个方法,synchronized块可以只锁部分代码
- 灵活性: synchronized块可以选择锁对象,synchronized方法固定锁this
推荐使用synchronized块:锁粒度更小,性能更好。
java
// ✅ 推荐:缩小锁范围
public void method() {
// 不需要同步的代码(如参数校验)
if (param == null) {
throw new IllegalArgumentException();
}
synchronized (this) {
// 只锁必要的代码
}
// 不需要同步的代码(如日志记录)
}
5.8 静态synchronized方法锁的是什么?
答案:锁的是Class对象,即类锁。
java
public class StaticSynchronizedMethod {
// 静态synchronized方法:锁的是StaticSynchronizedMethod.class
public static synchronized void staticMethod() {
// 等价于: synchronized (StaticSynchronizedMethod.class) { ... }
}
// 等价写法
public static void staticMethodEquivalent() {
synchronized (StaticSynchronizedMethod.class) {
// ...
}
}
}
类锁 vs 对象锁:
java
public class ClassLockVsObjectLock {
// 类锁(所有实例共享)
public static synchronized void staticMethod() {
System.out.println("类锁");
}
// 对象锁(每个实例独立)
public synchronized void instanceMethod() {
System.out.println("对象锁");
}
public static void main(String[] args) {
ClassLockVsObjectLock obj1 = new ClassLockVsObjectLock();
ClassLockVsObjectLock obj2 = new ClassLockVsObjectLock();
// 场景1:两个线程调用不同实例的instanceMethod()
// 结果:不互斥(不同对象锁)
new Thread(obj1::instanceMethod).start();
new Thread(obj2::instanceMethod).start();
// 场景2:两个线程调用staticMethod()
// 结果:互斥(同一个类锁)
new Thread(ClassLockVsObjectLock::staticMethod).start();
new Thread(ClassLockVsObjectLock::staticMethod).start();
// 场景3:一个线程调用staticMethod(),另一个调用instanceMethod()
// 结果:不互斥(不同的锁)
new Thread(ClassLockVsObjectLock::staticMethod).start();
new Thread(obj1::instanceMethod).start();
}
}
六、最佳实践与总结
6.1 并发编程三大特性保证
| 特性 | synchronized | volatile | Lock | Atomic类 |
|---|---|---|---|---|
| 原子性 | ✅ | ❌ (单次读写是原子的) | ✅ | ✅ |
| 可见性 | ✅ | ✅ | ✅ | ✅ |
| 有序性 | ✅ | ✅ (禁止重排序) | ✅ | ✅ |
保证方式:
- 原子性: synchronized、Lock、Atomic类的CAS操作
- 可见性: volatile、synchronized(unlock时刷新主内存)、Lock、final
- 有序性: volatile(内存屏障)、synchronized(串行执行)、happens-before规则
6.2 synchronized vs Lock选择指南
arduino
选择synchronized的场景:
├─ 简单的同步需求(如简单的计数器)
├─ 不需要高级特性(超时、中断、公平锁)
├─ JDK 1.6+版本(synchronized已优化,性能接近Lock)
└─ 代码简洁性优先
选择Lock的场景:
├─ 需要尝试非阻塞获取锁(tryLock)
├─ 需要可中断获取锁(lockInterruptibly)
├─ 需要超时获取锁(tryLock(timeout))
├─ 需要公平锁(new ReentrantLock(true))
├─ 需要读写分离(ReadWriteLock)
├─ 需要Condition多条件等待
└─ 需要更灵活的锁控制
示例对比:
java
// 场景1:简单同步 → 使用synchronized
public class SimpleCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
// 场景2:需要超时获取锁 → 使用Lock
public class TimeoutLock {
private final Lock lock = new ReentrantLock();
public boolean tryOperation() {
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 执行操作
return true;
} finally {
lock.unlock();
}
} else {
return false; // 超时,放弃操作
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
6.3 线程安全的设计原则
- 不可变对象优先: 使用final、不可变集合(Collections.unmodifiableXxx)
- 最小化锁范围: 只锁必要的代码,减少持锁时间
- 避免嵌套锁: 减少死锁风险,使用单锁或无锁设计
- 使用并发工具类: ConcurrentHashMap、AtomicInteger等,避免显式加锁
- 线程封闭: ThreadLocal、栈封闭,避免共享变量
- 无状态设计: Stateless服务,天然线程安全
6.4 高性能并发编程建议
-
减少锁竞争:
- 缩小锁粒度(细粒度锁、分段锁)
- 减少持锁时间
- 使用读写锁(ReadWriteLock)
- 使用无锁算法(CAS)
-
避免伪共享(False Sharing):
- 使用@Contended注解(JDK 8+)
- 手动填充缓存行
-
合理使用线程池:
- 避免频繁创建销毁线程
- 根据任务特性选择线程池类型(CPU密集型 vs IO密集型)
- 设置合理的队列大小和拒绝策略
-
异步化设计:
- 使用CompletableFuture、异步消息队列
- 解耦同步调用,提高吞吐量
6.5 代码审查Checklist
并发编程代码审查时,重点检查以下几点:
- 共享变量是否正确同步(synchronized、volatile、Lock)
- 是否存在竞态条件(check-then-act、read-modify-write)
- 是否正确使用wait/notify(在synchronized块中、使用while循环)
- 是否存在死锁风险(嵌套锁、锁顺序不一致)
- synchronized/Lock是否正确释放(使用finally块)
- volatile是否被误用(复合操作应使用synchronized)
- 线程停止是否优雅(使用标志位或interrupt,不使用stop)
- 是否使用了线程安全的集合类(ConcurrentHashMap vs HashMap)
- 是否考虑了可见性、原子性、有序性三大特性
6.6 总结
Java并发编程是一个复杂但必须掌握的领域。本文从线程基础出发,深入剖析了synchronized和volatile的底层原理,结合电商业务场景展示了实战应用,并通过生产案例分析了死锁和性能问题的排查方法。
核心要点:
- synchronized是Java内置的互斥锁,JDK 1.6后引入了锁升级机制(偏向锁→轻量级锁→重量级锁),性能大幅提升
- volatile保证可见性和有序性,但不保证原子性,适用于状态标志和一次性安全发布
- wait/notify必须在synchronized块中使用,防止丢失唤醒和保证原子性
- 线程安全的三大特性(原子性、可见性、有序性)是并发编程的核心
- 锁粒度优化是提升并发性能的关键,缩小锁范围、使用读写锁、无锁化设计
- 死锁的预防:避免嵌套锁、统一锁顺序、使用tryLock超时机制
在实际开发中,应根据业务场景选择合适的并发控制手段,遵循最佳实践,编写高性能、线程安全的代码。并发编程没有银弹,需要在正确性、性能、复杂度之间找到平衡点。