将从死锁产生的核心原理(结合 Android 场景)、具体产生过程、解决方案(Android 实践导向) 三个维度展开,并通过时序图直观呈现死锁形成与解决的完整流程。
一、线程死锁的核心定义与 Android 场景特性
线程死锁是指多个线程(如子线程、主线程)互相持有对方必需的锁资源,且均不释放已持有的锁,导致所有线程永久阻塞的状态。在 Android 中,死锁的危害尤为明显:
- 若主线程(UI 线程) 参与死锁,会直接触发 ANR(Application Not Responding);
- 若子线程(如网络线程、数据库线程) 死锁,会导致功能卡死(如数据同步失败、任务无法执行)。
Android 中常见的锁类型(也是死锁的核心载体)包括:
- 隐式锁:
synchronized(修饰方法、代码块,依赖 JVM 底层实现); - 显式锁:
java.util.concurrent.locks包下的ReentrantLock、ReadWriteLock(更灵活,支持中断、超时); - 其他锁:
Object.wait()/notify()的对象监视器锁、Room 数据库的事务锁等。
二、线程死锁的产生条件(四大必要条件)
死锁的产生必须同时满足以下 4 个条件,缺一不可。结合 Android 实际场景拆解如下:
| 必要条件 | 核心含义 | Android 场景示例 |
|---|---|---|
| 1. 互斥条件 | 锁资源只能被一个线程持有,同一时间无法被多个线程共享 | 用synchronized修饰的 “用户信息单例” 的updateUser()方法,同一时间只有一个线程能执行;Room 数据库的某张表锁,同一时间只能有一个写操作线程持有。 |
| 2. 持有并等待条件 | 线程持有一个锁后,不释放该锁,同时等待另一个锁 | 线程 A 持有 “用户数据锁”,还想获取 “订单数据锁” 以完成 “同步用户与订单” 的任务,此时会等待 “订单锁”。 |
| 3. 不可剥夺条件 | 线程持有的锁不能被强制夺走,只能由线程主动释放 | synchronized锁一旦被线程持有,只有线程执行完同步代码块或发生异常时才会释放,其他线程无法强制抢占;ReentrantLock若未调用unlock(),也不会被强制释放。 |
| 4. 循环等待条件 | 多个线程形成 “等待闭环”:线程 A 等线程 B 的锁,线程 B 等线程 A 的锁 | 线程 A 持有 “用户锁” 等 “订单锁”,线程 B 持有 “订单锁” 等 “用户锁”,形成循环等待。 |
三、Android 死锁产生的具体过程(案例 + 时序图)
以 “用户数据与订单数据同步” 这个典型 Android 场景为例,拆解死锁的完整产生过程。
1. 场景前提
-
存在两个锁资源:
userLock(用户数据锁)、orderLock(订单数据锁); -
两个同步方法:
updateUser():需持有userLock,更新用户信息;updateOrder():需持有orderLock,更新订单信息;
-
两个业务方法(存在锁嵌套):
syncUserThenOrder():先调用updateUser()(获userLock),再调用updateOrder()(等orderLock);syncOrderThenUser():先调用updateOrder()(获orderLock),再调用updateUser()(等userLock);
-
两个线程:
- 线程 1(子线程):执行
syncUserThenOrder(); - 线程 2(子线程):执行
syncOrderThenUser()。
- 线程 1(子线程):执行
2. 死锁产生时序图(Mermaid 语法,可直接渲染)
orderLock(订单锁)userLock(用户锁)线程2(执行syncOrderThenUser)线程1(执行syncUserThenOrder)orderLock(订单锁)userLock(用户锁)线程2(执行syncOrderThenUser)线程1(执行syncUserThenOrder)初始状态:所有锁均未被持有此时T1持有UL,T2持有OL循环等待形成(T1等OL,T2等UL),不可剥夺条件满足→死锁产生申请获取userLock(执行updateUser())获取成功(互斥条件满足)申请获取orderLock(执行updateOrder())获取成功(互斥条件满足)申请获取orderLock(执行updateOrder())被T2持有,进入等待(持有并等待条件满足)申请获取userLock(执行updateUser())被T1持有,进入等待(持有并等待条件满足)
3. 死锁产生步骤拆解
- 初始竞争:线程 1 和线程 2 几乎同时启动,分别申请
userLock和orderLock,因两个锁均未被持有,均获取成功(满足 “互斥条件”); - 持有并等待:线程 1 持有
userLock后,继续执行updateOrder(),需申请orderLock,但orderLock已被线程 2 持有,线程 1 进入BLOCKED状态(满足 “持有并等待条件”); - 循环等待:线程 2 持有
orderLock后,继续执行updateUser(),需申请userLock,但userLock已被线程 1 持有,线程 2 也进入BLOCKED状态; - 死锁固化:线程 1 等待线程 2 的
orderLock,线程 2 等待线程 1 的userLock,形成闭环(满足 “循环等待条件”);且两者的锁无法被强制剥夺(满足 “不可剥夺条件”),最终永久阻塞。
四、Android 并发死锁的解决方案(实践导向)
解决死锁的核心思路是破坏四大必要条件中的任意一个或多个,结合 Android 开发场景,给出可落地的方案:
方案 1:破坏 “循环等待” 条件 —— 统一锁的获取顺序(最常用)
核心逻辑
规定所有线程必须按固定顺序获取锁(如按锁对象的哈希值排序、按业务优先级排序),避免循环等待。
Android 实践示例
在上述案例中,强制所有线程必须先获取userLock,再获取orderLock,即使是syncOrderThenUser()方法,也需调整顺序:
// 锁顺序规则:先userLock,后orderLock
private final Object userLock = new Object();
private final Object orderLock = new Object();
// 业务方法1:同步用户→订单(符合顺序)
public void syncUserThenOrder() {
synchronized (userLock) { // 先拿userLock
updateUser();
synchronized (orderLock) { // 再拿orderLock
updateOrder();
}
}
}
// 业务方法2:同步订单→用户(调整为符合顺序)
public void syncOrderThenUser() {
synchronized (userLock) { // 强制先拿userLock(不再先拿orderLock)
updateUser();
synchronized (orderLock) {
updateOrder();
}
}
}
解决后时序图
orderLockuserLock线程2(syncOrderThenUser)线程1(syncUserThenOrder)orderLockuserLock线程2(syncOrderThenUser)线程1(syncUserThenOrder)T1持有UL,继续执行T1释放UL后,T2继续申请userLock获取成功申请userLock(按顺序)被T1持有,进入等待申请orderLock获取成功执行updateUser() + updateOrder()释放orderLock释放userLock获取成功申请orderLock获取成功执行updateUser() + updateOrder()释放orderLock释放userLock
方案 2:破坏 “持有并等待” 条件 —— 一次性获取所有锁
核心逻辑
线程在执行任务前,一次性申请所有必需的锁,若有任意一个锁无法获取,则释放已申请的锁并等待,避免 “持有部分锁等待其他锁”。
Android 实践示例(用ReentrantLock的tryLock()实现)
private final Lock userLock = new ReentrantLock();
private final Lock orderLock = new ReentrantLock();
public void syncData() {
boolean gotUserLock = false;
boolean gotOrderLock = false;
try {
// 一次性申请所有锁(超时100ms,避免永久等待)
gotUserLock = userLock.tryLock(100, TimeUnit.MILLISECONDS);
if (gotUserLock) {
gotOrderLock = orderLock.tryLock(100, TimeUnit.MILLISECONDS);
}
// 只有所有锁都获取成功,才执行业务逻辑
if (gotUserLock && gotOrderLock) {
updateUser();
updateOrder();
} else {
// 部分锁获取失败,重试或降级处理
Log.w("DeadLock", "部分锁获取失败,重试或降级");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
} finally {
// 释放已获取的锁(避免泄漏)
if (gotOrderLock) orderLock.unlock();
if (gotUserLock) userLock.unlock();
}
}
方案 3:破坏 “不可剥夺” 条件 —— 使用可中断锁
核心逻辑
使用支持中断的锁(如ReentrantLock的lockInterruptibly()),当线程等待锁超时或被标记为中断时,主动放弃等待并释放已持有的锁,打破 “不可剥夺”。
Android 实践示例(主线程中断子线程避免死锁)
// 子线程:执行可能死锁的任务
private Thread syncThread = new Thread(() -> {
try {
userLock.lockInterruptibly(); // 可中断锁
try {
orderLock.lockInterruptibly();
updateUser();
updateOrder();
} finally {
orderLock.unlock();
}
} catch (InterruptedException e) {
// 被中断,释放资源并退出
Log.i("DeadLock", "线程被中断,避免死锁");
Thread.currentThread().interrupt();
} finally {
if (userLock.isHeldByCurrentThread()) {
userLock.unlock();
}
}
});
// 主线程:检测到子线程可能死锁(如超时),中断子线程
public void checkAndInterrupt() {
syncThread.start();
// 超时检测(如5秒未完成则中断)
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (syncThread.isAlive()) {
syncThread.interrupt(); // 中断子线程,释放锁
}
}, 5000);
}
方案 4:避免嵌套锁 + 使用无锁并发工具(从源头减少锁竞争)
核心逻辑
- 减少锁的嵌套层级:将需多个锁的逻辑拆分为单一锁逻辑,或用更细粒度的锁(如
ReadWriteLock,读操作共享,写操作互斥); - 用无锁工具替代显式锁:Android 中优先使用
java.util.concurrent包下的线程安全容器或原子类,避免手动加锁。
Android 实践示例
-
用
ReadWriteLock优化读写锁竞争:private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Lock readLock = rwLock.readLock(); // 读锁共享 private final Lock writeLock = rwLock.writeLock(); // 写锁互斥 // 读操作(多线程可同时执行) public User getUser() { readLock.lock(); try { return userDao.queryUser(); } finally { readLock.unlock(); } } // 写操作(单线程执行) public void updateUser(User user) { writeLock.lock(); try { userDao.updateUser(user); } finally { writeLock.unlock(); } } -
用原子类替代
synchronized:若仅需原子更新变量(如计数器、状态标记),用AtomicInteger、AtomicBoolean等原子类(基于 CAS 操作,无锁):// 原子计数器(无需synchronized) private final AtomicInteger syncCount = new AtomicInteger(0); public void incrementSyncCount() { syncCount.incrementAndGet(); // 原子操作,线程安全 }
方案 5:死锁检测与恢复(Android 工具链支持)
当死锁已发生时,需通过工具检测并手动 / 自动恢复:
-
检测工具:
- Android Studio Profiler:打开 “App Inspection”→“Threads”,死锁线程会被标记为
BLOCKED,并显示 “Waiting for lock” 的具体信息; - jstack 命令:通过
adb shell ps | grep 包名获取进程 PID,再执行jstack PID,日志中会明确标注 “DEADLOCK” 及锁持有关系。
- Android Studio Profiler:打开 “App Inspection”→“Threads”,死锁线程会被标记为
-
恢复策略:
- 中断死锁线程:通过
Thread.interrupt()中断其中一个线程,释放锁; - 重启任务:检测到死锁后,终止当前任务并重新启动(如重新发起数据同步请求);
- 服务降级:若死锁影响核心功能,临时降级为本地缓存数据,保证 App 基础可用。
- 中断死锁线程:通过
五、总结
Android 线程死锁的本质是锁资源的不合理竞争与等待关系,解决的核心是:
- 预防优先:通过 “统一锁顺序”“一次性获取锁”“避免嵌套锁” 等方案,从源头破坏死锁的必要条件;
- 工具辅助:用 Profiler、jstack 等工具及时检测死锁,用
ReentrantLock、原子类等更灵活的并发工具替代传统锁; - 容错兜底:设置超时、中断机制,避免死锁导致 App 完全不可用。
在实际 Android 开发中,需结合业务场景(如 UI 线程、数据库、网络请求)选择合适的方案,优先保证主线程不阻塞、核心功能不卡死。