首页 > 编程笔记 > Java笔记 阅读:16

Java线程死锁的原因和解决方法(附带实例)

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去。

如下图所示的死锁状态,线程 A 己经持有了资源 1,它同时还想申请资源 2,可是此时线程 B 已经持有了资源 2,线程 A 只能等待。


图 1 死锁状态

反观线程 B 持有了资源 2,它同时还想申请资源 1,但是资源 1 已经被线程 A 持有,线程 B 只能等待。所以线程 A 和线程 B 就因为相互等待对方已经持有的资源,而进入了死锁状态。

那么,线程死锁的必备要素都有哪些呢?具体如下:

图 2 循环等待条件

【实例 1】死锁的实现。创建两个线程,名称分别为 threadA 和 threadB;创建两个资源(使用 newObject() 创建即可),名称分别为 resourceA 和 resourceB:
为了确保发生死锁现象,请使用 sleep() 方法创造该场景;执行代码,看是否会发生死锁,即线程 threadA 和 threadB 互相等待。代码如下:
public class Demo {
    private static Object resourceA = new Object(); // 创建资源 resourceA
    private static Object resourceB = new Object(); // 创建资源 resourceB

    public static void main(String[] args) throws InterruptedException {
        // 创建线程 threadA
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    System.out.println(Thread.currentThread().getName() + "获取 resourceA。");
                    try {
                        // sleep 1000 毫秒,确保此时 resourceB 已经进入 run()方法的同步模块中
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "开始申请 resourceB。");
                    synchronized (resourceB) {
                        System.out.println(Thread.currentThread().getName() + "获取 resourceB。");
                    }
                }
            }
        });
        threadA.setName("threadA");
        // 创建线程 threadB
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceB) {
                    System.out.println(Thread.currentThread().getName() + "获取 resourceB。");
                    try {
                        // sleep 1000 毫秒,确保此时 resourceA 已经进入 run()方法的同步模块中
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "开始申请 resourceA。");
                    synchronized (resourceA) {
                        System.out.println(Thread.currentThread().getName() + "获取 resourceA。");
                    }
                }
            }
        });
        threadB.setName("threadB");
        threadA.start();
        threadB.start();
    }
}
运行结果为:

threadB获取 resourceB。
threadA获取 resourceA。
threadA开始申请 resourceB。
threadB开始申请 resourceA。

避免死锁的方法

要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可,学过操作系统的读者应该都知道,目前只有请求并持有和环路等待条件是可以被破坏的。造成死锁的原因其实和申请资源的顺序有很大关系,使用资源申请的有序性原则就可避免死锁。

下面修改例 1,在其他条件保持不变的情况下,仅对之前的 threadB 的代码做如下修改,以避免死锁。代码如下:
Thread threadB = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized (resourceA) {
            System.out.println(Thread.currentThread().getName() + "获取 resourceA。");
            try {
                // sleep 1000 毫秒,确保此时 resourceA 已经进入 run()方法的同步模块中
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "开始申请 resourceA。");
        }
        synchronized (resourceB) {
            System.out.println(Thread.currentThread().getName() + "获取 resourceA。");
        }
    }
});
运行结果为:

threadB获取 resourceB。
threadB开始申请 resourceA。
threadB获取 resourceA。
threadA获取 resourceA。
threadA开始申请 resourceB。
threadA获取 resourceB。


综上,threadA 和 threadB 按照相同的顺序对 resourceA 和 resourceB 依次进行访问,避免了互相交叉持有等待的状态,因而避免了死锁的发生。

相关文章