授课语音

Java的死锁

1. 介绍

Java的死锁是指在多线程环境中,多个线程因争抢资源而导致相互等待,形成一个闭环,最终导致所有线程都无法继续执行,从而使程序无法响应。在实际开发中,死锁是一种常见的并发问题,需要谨慎对待。

必要条件

  1. 互斥条件:至少有一个资源是非共享的,一次只能被一个线程占用。
  2. 请求与保持条件:一个线程已经持有至少一个资源,并且在等待获取额外的资源,但这些额外的资源被其他线程持有。
  3. 不剥夺条件:资源不能被强制剥夺,必须由持有资源的线程主动释放。
  4. 循环等待条件:存在一个循环等待链,每个线程都在等待下一个线程持有的资源。

图示

Image___1397757896015032036===e5209f2588925f6723341cd1a420da74===2_1.png___

预防方法

  • 破坏请求与保持条件:在请求资源时,不持有其他资源;或者一次性请求所有需要的资源,避免后续请求。
  • 破坏不剥夺条件:在获取资源的请求失败时,释放已持有的资源。
  • 破坏循环等待条件:定义资源获取的顺序,确保所有线程按照相同的顺序获取资源。

避免方法

  • 银行家算法:在分配资源前,系统评估是否会导致死锁,如果不会则分配资源,否则进程等待。
  • 动态检查:在每个资源请求时进行动态检查,确保不会导致死锁。

2. 代码案例

死锁案例

package com.zhilitech.lock;

public class DeadlockDemo {

    public static void main(String[] args) {
        // 创建两个锁对象
        final Object lock1 = new Object();
        final Object lock2 = new Object();

        // 创建两个线程,它们将尝试获取不同的锁,导致死锁
        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 Object lock1; // 第一个锁
        private final Object lock2; // 第二个锁

        public Task(Object lock1, Object lock2) {
            this.lock1 = lock1;
            this.lock2 = lock2;
        }

        @Override
        public void run() {
            try {
                // 第一个锁
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName() + " 获取了锁1");

                    // 模拟一些操作
                    Thread.sleep(100);

                    // 第二个锁
                    synchronized (lock2) {
                        System.out.println(Thread.currentThread().getName() + " 获取了锁2");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace(); // 捕获并打印中断异常
            }
        }
    }
}

方案一,避免死锁嵌套

package com.zhilitech.lock;

public class DeadlockSolutionDemo {

    public static void main(String[] args) {
        // 创建两个锁对象
        final Object lock1 = new Object();
        final Object lock2 = new Object();

        // 创建两个线程,它们将以相同的顺序获取锁,避免死锁
        Thread thread1 = new Thread(new Task(lock1, lock2), "Thread-1");
        Thread thread2 = new Thread(new Task(lock1, lock2), "Thread-2");

        // 启动线程
        thread1.start();
        thread2.start();
    }

    // 任务类,尝试以相同的顺序获取两个锁以避免死锁
    static class Task implements Runnable {
        private final Object lock1; // 第一个锁
        private final Object lock2; // 第二个锁

        public Task(Object lock1, Object lock2) {
            this.lock1 = lock1;
            this.lock2 = lock2;
        }

        @Override
        public void run() {
            try {
                // 确保所有线程以相同的顺序获取锁
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName() + " 获取了锁1");

                    // 模拟一些操作
                    Thread.sleep(100);

                    synchronized (lock2) {
                        System.out.println(Thread.currentThread().getName() + " 获取了锁2");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace(); // 捕获并打印中断异常
            }
        }
    }
}

方案二,使用锁超时

package com.zhilitech.lock;

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

public class DeadlockSolutionWithTimeoutDemo {

    public static void main(String[] args) {
        // 创建两个可重入锁对象
        final Lock lock1 = new ReentrantLock();
        final Lock lock2 = new ReentrantLock();

        // 创建两个线程,它们将尝试获取锁并使用超时机制来避免死锁
        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 lock1; // 第一个可重入锁
        private final Lock lock2; // 第二个可重入锁

        public Task(Lock lock1, Lock lock2) {
            this.lock1 = lock1;
            this.lock2 = lock2;
        }

        @Override
        public void run() {
            try {
                // 尝试以锁1和锁2的顺序获取锁,并设置超时
                boolean lock1Acquired = lock1.tryLock(1000, TimeUnit.MILLISECONDS);
                if (lock1Acquired) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " 获取了锁1");

                        // 模拟一些操作
                        Thread.sleep(100);

                        boolean lock2Acquired = lock2.tryLock(1000, TimeUnit.MILLISECONDS);
                        if (lock2Acquired) {
                            try {
                                System.out.println(Thread.currentThread().getName() + " 获取了锁2");
                            } finally {
                                lock2.unlock(); // 释放锁2
                            }
                        } else {
                            System.out.println(Thread.currentThread().getName() + " 未能获取锁2,放弃");
                        }
                    } finally {
                        lock1.unlock(); // 释放锁1
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + " 未能获取锁1,放弃");
                }
            } catch (InterruptedException e) {
                e.printStackTrace(); // 捕获并打印中断异常
            }
        }
    }
}

通过这些案例和解决方案,希望大家对Java的死锁问题有更深入的理解,并掌握一些有效的预防和解决方法。

去1:1私密咨询

系列课程: