怎么避免死锁

参考答案

并发程序一旦死锁,通常只能重启应用。解决死锁问题,最好的办法是:避免死锁。

一、死锁发生的条件

  1. 互斥,共享资源只能被一个线程占用;
  2. 占有且等待,线程 t1 已经取得共享资源 s1,尝试获取共享资源 s2 的时候,不释放共享资源 s1;
  3. 不可抢占,其他线程不能强行抢占线程 t1 占有的资源 s1;
  4. 循环等待,线程 t1 等待线程 t2 占有的资源,线程 t2 等待线程 t1 占有的资源。

二、避免死锁的方法

  1. 对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。
  2. 对于第一个条件 “互斥” 是不能破坏的,因为加锁就是为了保证互斥。
  3. 其他三个条件,可以做以下尝试:
  • 一次性申请所有的资源,破坏 “占有且等待” 条件;
  •  占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 “不可抢占” 条件;
  •  按序申请资源,破坏 “循环等待” 条件。

三、编程中的最佳实践

  • 使用 Lock 的 tryLock(long timeout, TimeUnit unit)的方法,设置超时时间,超时可以退出防止死锁;
  • 尽量使用并发工具类代替加锁;
  • 尽量降低锁的使用粒度;
  • 尽量减少同步的代码块。

四、实例

1.  使用管理类一次性申请所有的资源,破坏 “占有且等待” 条件实例:

import java.util.HashSet;
import java.util.Set;

/**
 * 测试 一次性申请所有的资源,破坏 "占有且等待" 条件示例
 * @author ConstXiong
 * @date 2019-09-24 14:04:12
 */
public class TestBreakLockAndWait {

    //单例的资源管理类
    private final static Manger manager = new Manger();
    
    //资源1
    private static Object res1 = new Object();
    
    //资源2
    private static Object res2 = new Object();
    
    public static void main(String[] args) {
        new Thread(() -> {
            boolean applySuccess = false;
            while (!applySuccess) {
                //向管理类,申请res1和res2,申请失败,重试
                applySuccess = manager.applyResources(res1, res2);
                if (applySuccess) {
                    try {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
                        synchronized (res1) {
                            System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
                            //休眠 1秒
                            try {
                                Thread.sleep(1000);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            synchronized (res2) {
                                System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
                            }
                        }
                    } finally {
                        manager.returnResources(res1, res2);//归还资源
                    }
                } else {
                    System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源失败");
                    //申请失败休眠 200 毫秒后重试
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
        new Thread(() -> {
            boolean applySuccess = false;
            while (!applySuccess) {
                //向管理类,申请res1和res2,申请失败,重试
                applySuccess = manager.applyResources(res1, res2);
                if (applySuccess) {
                    try {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
                        synchronized (res2) {
                            System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
                            //休眠 1秒
                            try {
                                Thread.sleep(1000);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            synchronized (res1) {
                                System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
                            }
                        }
                    } finally {
                        manager.returnResources(res1, res2);//归还资源
                    }
                } else {
                    System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源失败");
                    //申请失败休眠 200 毫秒后重试
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
    }
    
}

/**
 * 资源申请、归还管理类
 * @author ConstXiong
 * @date 2019-09-24 14:10:57
 */
class Manger {
    
    //资源存放集合
    private Set<Object> resources = new HashSet<Object>();
    
    /**
     * 申请资源
     * @param res1
     * @param res2
     * @return
     */
    synchronized boolean applyResources(Object res1, Object res2) {
        if (resources.contains(res1) || resources.contains(res1)) {
            return false;
        } else {
            resources.add(res1);
            resources.add(res2);
            return true;
        }
    }
    
    /**
     * 归还资源
     * @param res1
     * @param res2
     */
    synchronized void returnResources(Object res1, Object res2) {
        resources.remove(res1);
        resources.remove(res2);
    }
    
}

结果:

线程-1 在线程-0 释放完资源后,才能成功申请 res1 和 res2 的锁。

线程:Thread-0 申请 res1、res2 资源成功
线程:Thread-0 获取到 res1 资源的锁
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-0 获取到 res2 资源的锁
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源成功
线程:Thread-1 获取到 res1 资源的锁
线程:Thread-1 获取到 res2 资源的锁

 

2.  使用 Lock 的 tryLock() 方法,获取锁失败释放所有资源,破坏 “不可抢占” 条件实例

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

/**
 * 测试 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件
 * @author ConstXiong
 * @date 2019-09-24 14:50:51
 */
public class TestBreakLockOccupation {
    
    private static Random r = new Random(); 

    private static Lock lock1 = new ReentrantLock();
    
    private static Lock lock2 = new ReentrantLock();
    
    public static void main(String[] args) {
        new Thread(() -> {
            //标识任务是否完成
            boolean taskComplete = false;
            while (!taskComplete) {
                lock1.lock();
                System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功");
                try {
                    //随机休眠,帮助造成死锁环境
                    try {
                        Thread.sleep(r.nextInt(30));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    
                    //线程 0 尝试获取 lock2
                    if (lock2.tryLock()) {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功");
                        try {
                            taskComplete = true;
                        } finally {
                            lock2.unlock();
                        }
                    } else {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 失败");
                    }
                } finally {
                    lock1.unlock();
                }
                
                //随机休眠,避免出现活锁
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        new Thread(() -> {
            //标识任务是否完成
            boolean taskComplete = false;
            while (!taskComplete) {
                lock2.lock();
                System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功");
                try {
                    //随机休眠,帮助造成死锁环境
                    try {
                        Thread.sleep(r.nextInt(30));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    
                    //线程2 尝试获取锁 lock1
                    if (lock1.tryLock()) {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功");
                        try {
                            taskComplete = true;
                        } finally {
                            lock1.unlock();
                        }
                    } else {
                        System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 失败");
                    }
                } finally {
                    lock2.unlock();
                }
                
                //随机休眠,避免出现活锁
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    
}

结果:

线程:Thread-0 获取锁 lock1 成功
线程:Thread-1 获取锁 lock2 成功
线程:Thread-1 获取锁 lock1 失败
线程:Thread-1 获取锁 lock2 成功
线程:Thread-0 获取锁 lock2 失败
线程:Thread-1 获取锁 lock1 成功
线程:Thread-0 获取锁 lock1 成功
线程:Thread-0 获取锁 lock2 成功

 

3.  按照一定的顺序加锁,破坏 “循环等待” 条件实例

/**
 * 测试 按序申请资源,破坏 "循环等待" 条件
 * @author ConstXiong
 * @date 2019-09-24 15:26:23
 */
public class TestBreakLockCircleWait {

    private static Object res1 = new Object();
    
    private static Object res2 = new Object();
    
    
    public static void main(String[] args) {
        new Thread(() -> {
            Object first = res1;
            Object second = res2;
            //比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode > res2,交换 first 和 second。保证 hashCode 小的对象先加锁
            if (res1.hashCode() > res2.hashCode()) {
                first = res2;
                second = res1;
            }
            synchronized (first) {
                System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + first + " 锁成功");
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized(second) {
                    System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + second + " 锁成功");
                }
            }
        }).start();
        
        new Thread(() -> {
            Object first = res1;
            Object second = res2;
            //比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode > res2,交换 first 和 second。保证 hashCode 小的对象先加锁
            if (res1.hashCode() > res2.hashCode()) {
                first = res2;
                second = res1;
            }
            synchronized (first) {
                System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + first + " 锁成功");
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized(second) {
                    System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + second + " 锁成功");
                }
            }
        }).start();
    }
    
}

 

结果:

线程:Thread-0获取资源 java.lang.Object@7447157c 锁成功
线程:Thread-0获取资源 java.lang.Object@7a80f45c 锁成功
线程:Thread-1获取资源 java.lang.Object@7447157c 锁成功
线程:Thread-1获取资源 java.lang.Object@7a80f45c 锁成功

 

以上,是Java面试题【怎么避免死锁】的参考答案。

输出,是最好的学习方法

欢迎在评论区留下你的问题、笔记或知识点补充~

—end—

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧