在并发编程和操作系统当中两个及以上的线程互相持有对方所需要的资源又不释放资源导致任何一个线程都无法继续执行一直卡住这就是死锁。举一个比较容易理解的例子线程A获取了锁1线程B获取了锁2现在A在不释放锁1的情况下获取锁2B在不释放锁2的情况下获取锁1都获取不了一直僵持就出现了死锁的情况。目录产生死锁的四个必要条件解决死锁的方法银行家算法按序加锁比较银行家算法和按序加锁的普适性和落地性产生死锁的四个必要条件那么在什么情况下会产生死锁呢死锁产生有四个必要条件四个都要满足1互斥资源同一时间只能被一个线程占用如果线程A拥有锁1线程B想要获取锁1就要阻塞等待一直到线程A释放锁2请求与保持持有资源的同时获取其他的资源线程A拥有了锁1还想要获取锁23不可剥夺资源只能主动释放不能强行剥夺一个线程拥有了锁1只有他主动释放了锁1其他线程才能竞争锁14循环等待形成了一个资源等待链比如线程A拥有锁1获取锁2线程B拥有锁2获取锁3线程C拥有锁3获取锁1就形成了一个A-B-C-A这样的环路等待链解决死锁的方法既然我们知道需要同时满足上面的四个条件才能出现死锁那解决死锁只需要打破其中一个条件成立即可事实上在实际开发中最常用、落地性最强的的两种是银行家算法避免死锁、按序加锁破坏循环等待银行家算法算法思想四个矩阵:Available|Max|Need|Allocation 系统提前知道每个线程最多需要多少个资源 每个进程申请资源时先预先分配 检查分配后是否存在安全序列 安全-允许分配不安全-阻塞等待具体的例子以经典的哲学家就餐问题为例子5个哲学家5根筷子就餐每个人拿起左边的筷子然后拿起右边的筷子吃饭然后放下筷子最坏的情况就是所有人拿起左边的筷子都在等右边的筷子发现没有筷子没人有两根筷子都吃不了饭出现了死锁使用银行家算法系统需要预知每个哲学家最多需要2根筷子Max矩阵哲学家想吃饭时先不分配筷子先预分配看是否存在安全序列不产生死锁存在就按照安全序列分配筷子如果不存在不分配让哲学家继续分配Available:当前剩余多少可用的筷子Allocation:每个哲学家分别分配了多少根筷子Need:每个哲学家被分配了筷子后每位还需要多少根筷子NeedMax-AllocationMax:每个哲学家最多需要多少根筷子按序加锁死锁的本质是循环等待除了避免嵌套锁之外统一加锁顺序可以说是最有效的方案了下面的例子就是用了统一加锁顺序的方法。先提及如果是加锁需要确定锁对象然后使用synchronized(锁对象){}; 如果执行语句在synchronized的}之外说明已经释放了对这个锁对象加的锁接下来看下列代码发生死锁的情况线程t1获取了锁1在不释放手中的锁1的情况下尝试获取锁1同样的线程t2获取了锁1在不释放手中的锁2的情况下尝试获取锁1二者都获取想要的锁失败并且陷入了死锁。注意到下列代码中有双重锁即synchronized(A) {synchronized(B) {}}在java中是允许出现双重锁也叫做双重检查锁DCL,目的是为了兼顾线程安全和性能高效。public class ThreadDemo22 { public static void main(String[] args) { //创建两个锁对象 Object lock1 new Object(); Object lock2 new Object(); //使用Lambda表达式创建两个线程 Thread t1 new Thread(() - { synchronized (lock1) { System.out.println(线程t1获取了锁1); try { Thread.sleep(1000);//线程t1休眠1s } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (lock2) { System.out.println(线程t1尝试获取锁2); } } }); Thread t2 new Thread(() - { synchronized (lock2) { System.out.println(线程t2获取了锁2); try { Thread.sleep(1000);//线程t2休眠1s } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (lock1) { System.out.println(线程t2尝试获取锁1); } } }); //使用start创建并启动线程 t1.start(); t2.start(); } }代码运行结果那解决上面的死锁可以把代码改成我们不再交叉获取锁而是两个线程按照相同的顺序获取锁那么线程t1获取了锁1然后因为没有其他线程获取锁2t1线程获取了锁2完成了执行之后释放了锁1和锁2线程t2获取锁1和锁2package Thread; public class ThreadDemo23 { public static void main(String[] args) { //创建两个锁对象 Object lock1 new Object(); Object lock2 new Object(); //使用Lambda表达式创建两个线程 Thread t1 new Thread(() - { synchronized (lock1) { System.out.println(线程t1获取了锁1); try { Thread.sleep(1000);//线程t1休眠1s } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (lock2) { System.out.println(线程t1尝试获取锁2并成功); } } }); Thread t2 new Thread(() - { synchronized (lock1) { System.out.println(线程t2获取了锁1); try { Thread.sleep(1000);//线程t2休眠1s } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (lock2) { System.out.println(线程t2尝试获取锁2并成功); } } }); //使用start创建并启动线程 t1.start(); t2.start(); } }代码运行结果比较银行家算法和按序加锁的普适性和落地性事实上对比银行家算法和按序加锁这两种解决死锁的方法按序加锁实现简单、性能损耗小是工业界最常使用的的方案了而银行家算法理论更加严谨但是需要提前知道各个资源数、开销大在实际的工程中几乎不使用。最后我会不定时地更新博客如果觉得文章不错可以关注一下这个努力的博主有错误的地方欢迎指出有任何问题可以在评论区交流等等。