文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-11-28 13:50

关注

一、死锁的产生条件

死锁的发生通常需要满足以下四个条件:

当上面这四个条件同时满足时,就会发生死锁。

二、死锁的案例

如下图:线程1持有 ResourceA的锁并等待 ResourceB的锁,线程2持有 ResourceB的锁并等待ResourceA的锁,这样Thread1和Thread2就形成了死锁。

下面,我们通过一个简单的Java示例来描述上面死锁的情况:

public class DeadlockExample {

    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: Holding lock 1...");

                try { Thread.sleep(10); } catch (InterruptedException e) {}

                System.out.println("Thread 1: Waiting for lock 2...");
                synchronized (lock2) {
                    System.out.println("Thread 1: Holding lock 1 & 2...");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock 2...");

                try { Thread.sleep(10); } catch (InterruptedException e) {}

                System.out.println("Thread 2: Waiting for lock 1...");
                synchronized (lock1) {
                    System.out.println("Thread 2: Holding lock 1 & 2...");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

在这个例子中,thread1首先获得lock1,然后等待lock2,而thread2首先获得lock2,然后等待lock1。这就导致了死锁,因为两个线程都在等待对方持有的锁并且无法继续执行。

三、避免死锁的方法

在 Java中,避免的死锁的方式还是比较丰富的,这里我列举了一些常见的避免死锁的方法:

1. 资源排序法

资源排序法(Resource Ordering)是通过对资源进行全局排序,确保所有线程都按照相同的顺序获取锁,从而避免循环等待。例如,线程在获取多个锁时,总是先获取编号小的锁,再获取编号大的锁。

2. 尝试锁

尝试锁(Try Lock)是使用tryLock()方法来代替lock()方法。在尝试获取锁时,设置一个超时时间,如果在规定时间内无法获得锁,则放弃获取锁,从而避免死锁。

Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();

try {
    if (lock1.tryLock(50, TimeUnit.MILLISECONDS)) {
        try {
            if (lock2.tryLock(50, TimeUnit.MILLISECONDS)) {
                try {
                    // critical section
                } finally {
                    lock2.unlock();
                }
            }
        } finally {
            lock1.unlock();
        }
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

3. 超时放弃法

超时放弃法(Timeout and Retry)是指为线程等待资源的时间设置上限,如果超过这个时间还没有获得资源,则主动放弃,并稍后重试。

如下示例展示了超时放弃法的实现:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class TimeoutAvoidanceExample {

    private static final Lock lock1 = new ReentrantLock();
    private static final Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Task(lock1, lock2), "Thread-1");
        Thread thread2 = new Thread(new Task(lock2, lock1), "Thread-2");

        thread1.start();
        thread2.start();
    }

    static class Task implements Runnable {
        private final Lock firstLock;
        private final Lock secondLock;

        public Task(Lock firstLock, Lock secondLock) {
            this.firstLock = firstLock;
            this.secondLock = secondLock;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    // 尝试获取第一个锁
                    if (firstLock.tryLock(50, TimeUnit.MILLISECONDS)) {
                        try {
                            // 尝试获取第二个锁
                            if (secondLock.tryLock(50, TimeUnit.MILLISECONDS)) {
                                try {
                                    // 成功获取两个锁后执行关键操作
                                    System.out.println(Thread.currentThread().getName() + ": Acquired both locks, performing task.");
                                    break; // 退出循环,任务完成
                                } finally {
                                    secondLock.unlock();
                                }
                            }
                        } finally {
                            firstLock.unlock();
                        }
                    }
                    // 如果未能获取锁,则稍后重试
                    System.out.println(Thread.currentThread().getName() + ": Could not acquire both locks, retrying...");
                    Thread.sleep(10); // 等待一段时间后重试
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

代码解读:

4. 死锁检测

在某些情况下,可以使用死锁检测算法来发现死锁并采取措施。Java中的java.lang.management包提供了检测死锁的工具。

ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();

if (deadlockedThreads != null) {
    System.out.println("Deadlock detected!");
    // Handle the deadlock situation
}

5. 减少锁的持有时间

尽量缩短锁的持有时间,确保在锁内执行的操作尽可能少,从而减少发生死锁的机会。

6. 使用更高层次的并发工具

Java提供了许多高级并发工具类,如java.util.concurrent包下的ConcurrentHashMap、Semaphore、CountDownLatch等,这些工具类在设计时就考虑了并发访问的安全性并减少了死锁的可能性。

7. 避免嵌套锁

避免嵌套锁(Avoid Nested Locks,尽量避免一个线程在持有一个锁的同时去获取另一个锁,因为这会增加发生死锁的风险。

四、总结

死锁是多线程编程中一个复杂而又让人头疼的问题,在实际开发中,死锁问题有时候发生还很难找到原因,因此,在日常开发中遵循良好的编程实践,可以有效地避免和处理死锁。

作为技术人员,需要掌握死锁产生根本原因,这样,即便死锁发生了也能快速的定位和解决。

来源:猿java内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯