汇知百科
白蓝主题五 · 清爽阅读
首页  > 系统软件

线程同步中的死锁问题 日常维护方法与实用案例

线程同步中的死问题

在多线程编程中,多个线程常常需要访问共享资源。为了防止数据混乱,程序员会使用锁机制来实现线程同步。但当锁的使用不当,就可能引发一个棘手的问题——死锁。

死锁指的是两个或多个线程相互等待对方释放锁,导致所有线程都无法继续执行的状态。就像两个人在狭窄的走廊里迎面走来,谁也不肯让路,结果谁也过不去。

死锁的四个必要条件

要发生死锁,必须同时满足以下四个条件:

互斥条件:某个资源一次只能被一个线程占用。

占有并等待:线程已经持有了至少一个资源,还在等待获取其他被占用的资源。

不可抢占:已分配给线程的资源不能被其他线程强行拿走,只能由该线程主动释放。

循环等待:存在一个线程的等待环路,比如线程A等线程B,线程B等线程C,线程C又等线程A。

一个典型的死锁示例

假设两个线程分别尝试按不同顺序获取两把锁:

Thread 1:
synchronized(lockA) {
sleep(100);
synchronized(lockB) {
// 执行操作
}
}

Thread 2:
synchronized(lockB) {
sleep(100);
synchronized(lockA) {
// 执行操作
}
}

这种情况下,线程1先拿到lockA,线程2先拿到lockB,接着它们都试图获取对方持有的锁。由于都在等待,最终陷入僵局。

如何避免死锁

最简单的方法是保证所有线程以相同的顺序获取锁。比如都先申请lockA,再申请lockB,这样就不会形成循环等待。

另一个办法是使用带超时的锁请求。Java中的java.util.concurrent.locks.ReentrantLock支持tryLock(long timeout, TimeUnit unit)方法,如果在指定时间内拿不到锁,线程可以选择放弃,转而处理其他任务,而不是无限等待。

ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();

// 线程尝试获取锁
boolean acquiredA = lockA.tryLock(1, TimeUnit.SECONDS);
if (acquiredA) {
try {
boolean acquiredB = lockB.tryLock(1, TimeUnit.SECONDS);
if (acquiredB) {
try {
// 安全执行临界区代码
} finally {
lockB.unlock();
}
}
} finally {
lockA.unlock();
}
}

此外,尽量减少锁的持有时间,只在真正需要时才加锁,也能降低死锁发生的概率。

实际开发中,数据库系统也会遇到类似情况。比如两个事务互相等待对方释放行锁,数据库通常会通过死锁检测机制自动中断其中一个事务,避免系统卡死。

编写多线程程序时,不能只关注功能正确性,还得留意资源调度的合理性。一个看似无害的同步逻辑,可能在高并发下暴露出隐藏的死锁风险。