Java面试必备:Java中什么情况会导致死锁?如何避免?

Java面试题 - Java中什么情况会导致死锁?如何避免?


一、什么是死锁

死锁(Deadlock)是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,这些线程都将无法继续执行下去。

请求锁2
请求锁1
线程A持有锁1
线程B持有锁2

二、Java中导致死锁的四种必要条件

1. 互斥条件

资源一次只能被一个线程占用。

2. 占有且等待

线程持有至少一个资源,并等待获取其他被占用的资源。

3. 不可抢占条件

已分配给线程的资源,不能被其他线程强行夺取。

4. 循环等待条件

存在一个线程等待的循环链,每个线程都在等待下一个线程所占用的资源。

等待
等待
等待
线程1
资源2
线程2
资源3
线程3
资源1

三、Java中常见的死锁场景

1. 嵌套锁获取

// 线程1
synchronized(lockA) {
    synchronized(lockB) {
        // 操作
    }
}

// 线程2
synchronized(lockB) {
    synchronized(lockA) {
        // 操作
    }
}

2. 资源竞争

// 账户转账死锁示例
public void transfer(Account from, Account to, int amount) {
    synchronized(from) {
        synchronized(to) {
            from.debit(amount);
            to.credit(amount);
        }
    }
}

3. 线程间通信不当

// 生产者消费者死锁示例
public synchronized void put(Object item) {
    while (isFull()) {
        wait(); // 可能永远等待
    }
    // 添加item
    notify();
}

四、如何避免死锁

1. 破坏死锁的必要条件

(1) 避免嵌套锁
获取锁的顺序
统一获取锁的顺序
例如按照锁对象的hashCode顺序
(2) 使用定时锁
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
    try {
        // 操作资源
    } finally {
        lock.unlock();
    }
} else {
    // 执行替代方案
}
(3) 开放调用

避免在持有锁时调用外部方法。

2. 死锁检测与恢复

// 使用ThreadMXBean检测死锁
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads();
if (threadIds != null) {
    // 处理死锁
}

3. 使用高级并发工具

// 使用ReentrantLock替代synchronized
Lock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区代码
} finally {
    lock.unlock();
}

五、最佳实践

  1. 锁顺序:总是以固定的全局顺序获取锁
  2. 锁时限:使用tryLock设置超时时间
  3. 死锁检测:实现死锁检测机制
  4. 减少锁粒度:使用更细粒度的锁
  5. 避免持有锁时执行耗时操作
避免死锁策略
减少锁竞争
锁顺序
锁超时
死锁检测
减少锁粒度
使用并发工具

六、总结

死锁是多线程编程中的常见问题,理解其产生条件和避免方法对于编写健壮的并发程序至关重要。通过合理的锁策略、资源管理方案和工具使用,可以有效地预防和解决死锁问题。

记住预防胜于治疗,在设计和编码阶段就考虑死锁问题,比后期调试和修复要高效得多。

你可能感兴趣的:(#,Java热门面试题200道,java,面试,Java并发)