首页 智谱AI文章正文

Java死锁的成因与避免策略,从原理到实践

智谱AI 2026年06月03日 20:56 1 admin

在Java并发编程中,死锁是一个经典且隐蔽的问题,它指的是多个线程因互相等待对方释放资源而陷入无限阻塞,导致程序无法继续执行,甚至引发系统崩溃,线程A持有锁1等待锁2,线程B持有锁2等待锁1,两者互相等待,谁也无法继续前进,死锁一旦发生,通常需要重启程序才能解决,因此理解其成因并掌握避免策略,是Java开发者必备的核心能力,本文将从死锁的成因出发,详细讲解Java中避免死锁的实用策略,并结合代码示例帮助读者落地实践。

死锁的成因:四个必要条件

根据操作系统理论,死锁的发生必须同时满足以下四个条件(缺一不可):

  1. 互斥条件:资源必须被独占占用,即一个资源在同一时间只能被一个线程持有,Java中的synchronized锁或ReentrantLock,都要求同一时间只有一个线程能获取锁。
  2. 占有并等待:线程至少持有一个资源,同时等待获取其他被其他线程占用的资源,线程A已持有锁1,但仍在等待锁2,且不会主动释放锁1。
  3. 不可剥夺条件:资源不能被强制剥夺,只能由持有线程主动释放。synchronized锁的释放必须等待线程执行完同步代码块或方法,无法被其他线程强制抢走。
  4. 循环等待:存在线程资源的循环等待链,即线程A等待线程B的资源,线程B等待线程C的资源……最终线程C等待线程A的资源,形成闭环。

只要破坏其中任意一个条件,就能避免死锁,接下来我们结合Java特性,具体讲解如何通过破坏这些条件来预防死锁。

避免死锁的核心策略

破坏“占有并等待”条件:一次性获取所有资源

“占有并等待”是指线程已持有一部分资源,同时等待其他资源,我们可以通过“一次性申请所有需要的资源”来破坏这一条件:要么一次性获取所有资源,要么一个都不获取,避免“部分持有、部分等待”的情况。

实现方式:使用tryLock设置超时

Java的ReentrantLock提供了tryLock(long timeout, TimeUnit unit)方法,允许线程在指定时间内尝试获取锁,超时后则放弃当前锁的等待,避免无限阻塞。

示例代码:

import java.util.concurrent.locks.ReentrantLock;
public class DeadlockAvoidanceByTryLock {
    private static final ReentrantLock lock1 = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            try {
                // 尝试获取lock1,最多等待1秒
                if (lock1.tryLock(1, java.util.concurrent.TimeUnit.SECONDS)) {
                    try {
                        System.out.println("线程A获取lock1,尝试获取lock2...");
                        // 尝试获取lock2,最多等待1秒
                        if (lock2.tryLock(1, java.util.concurrent.TimeUnit.SECONDS)) {
                            try {
                                System.out.println("线程A成功获取lock1和lock2,执行任务...");
                            } finally {
                                lock2.unlock();
                            }
                        } else {
                            System.out.println("线程A获取lock2超时,释放lock1");
                        }
                    } finally {
                        lock1.unlock();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread threadB = new Thread(() -> {
            try {
                // 尝试获取lock2,最多等待1秒
                if (lock2.tryLock(1, java.util.concurrent.TimeUnit.SECONDS)) {
                    try {
                        System.out.println("线程B获取lock2,尝试获取lock1...");
                        // 尝试获取lock1,最多等待1秒
                        if (lock1.tryLock(1, java.util.concurrent.TimeUnit.SECONDS)) {
                            try {
                                System.out.println("线程B成功获取lock1和lock2,执行任务...");
                            } finally {
                                lock1.unlock();
                            }
                        } else {
                            System.out.println("线程B获取lock1超时,释放lock2");
                        }
                    } finally {
                        lock2.unlock();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadA.start();
        threadB.start();
    }
}

说明

  • 线程A先尝试获取lock1,超时时间为1秒;如果成功,再尝试获取lock2,同样设置1秒超时。
  • 如果获取lock2超时,线程A会释放已获取的lock1,避免长期占用。
  • 线程B的逻辑类似,只是锁的顺序相反,通过超时机制,避免了“互相等待”的死锁情况。

破坏“循环等待”条件:固定锁的获取顺序

Java死锁的成因与避免策略,从原理到实践

快讯网 - 分享生活资讯热点话题综合门户网站-上海锐衡凯网络科技 备案号:沪ICP备2023039795号 内容仅供参考 本站内容均来源于网络,如有侵权,请联系我们删除:597817868@qq.com