Java并发核心知识体系精讲---死锁的前世今生

本文详细讲解了Java并发中的死锁问题,包括死锁的概念、影响、示例及如何定位和修复死锁。通过实际案例分析了死锁的4个必要条件,并探讨了如何在实际工程中避免死锁,以及死锁与其他活性问题如活锁和饥饿的区分。
摘要由CSDN通过智能技术生成

声明:本文是自己自学慕课网悟空老师的《Java并发核心知识体系精讲》的死锁部分后整理而成课程笔记。

课程链接如下:https://coding.imooc.com/class/362.html

如有侵权,请私信我并第一时间删除本文。

image-20220211172820677

image-20220211174107495

image-20220211174221413

image-20220213151257261

1. 死锁是什么?有什么危害

1.1什么是死锁

  • 发生在并发中

  • 互不相让:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进, 导致程序陷入无尽的阻塞,这就是死锁。

  • 一图胜千言

    image-20220211174525062

  • 多个线程造成死锁的情况

如果多个线程之间的依赖关系是环形,存在环路的锁的依赖关系,那么也可能会发生死锁

image-20220212192135055

1.2 死锁的影响

死锁的影响在不同系统中是不一样的,这取决于系统对死锁的处理能力

◆数据库中:检测并放弃事务

◆JVM中:无法自动处理

image-20220213152431795

2. 发生死锁例子

image-20220213160448472

image-20220211194619428

2.1 最简单的死锁

/**
 * 描述:     必定发生死锁的情况
 */
public class MustDeadLock implements Runnable {
   int flag = 1;static Object o1 = new Object();
    static Object o2 = new Object();public static void main(String[] args) {
   
        MustDeadLock r1 = new MustDeadLock();
        MustDeadLock r2 = new MustDeadLock();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
    }@Override
    public void run() {
   
        System.out.println("flag = " + flag);
        if (flag == 1) {
   
            synchronized (o1) {
   
                try {
   
                    Thread.sleep(500);
                } catch (InterruptedException e) {
   
                    e.printStackTrace();
                }
                synchronized (o2) {
   
                    System.out.println("线程1成功拿到两把锁");
                }
            }
        }
        if (flag == 0) {
   
            synchronized (o2) {
   
                try {
   
                    Thread.sleep(500);
                } catch (InterruptedException e) {
   
                    e.printStackTrace();
                }
                synchronized (o1) {
   
                    System.out.println("线程2成功拿到两把锁");
                }
            }
        }
    }
}

分析

  • 当类的对象flag=1时(T1) ,先锁定O1,睡眠500毫秒, 然后锁定O2 ;
  • 而T1在睡眠的时候另一个flag=0的对象(T2)线程启动,先锁定O2,睡眠500毫秒,等待T1释放01 ;
  • T1睡眠结束后需要锁定O2才能继续执行,而此时O2已被T2锁定;
  • T2睡眠结束后需要锁定O1才能继续执行,而此时O1已被T1锁定;
  • T1、T2相互等待,都需要对方锁定的资源才能继续执行,从而死锁。

image-20220212200127427

注意看退出信号: Process finished with exit code 130(interrupted by signal 2: SIGINT) ,是不正常退出的信号,对比正常结束的程序的结束信号是0

2.2 实际生产中的例子:转账

  • 需要两把锁

  • 获取两把锁成功,且余额大于0 ,则扣除转出人,增加收款人的余额,是原子操作

  • 顺序相反导致死锁

    /**
     * 描述:     转账时候遇到死锁,一旦打开注释,便会发生死锁
     */
    public class TransferMoney implements Runnable {
         int flag = 1;
        static Account a = new Account(500);
        static Account b = new Account(500);
        static Object lock = new Object();public static void main(String[] args) throws InterruptedException {
         
            TransferMoney r1 = new TransferMoney();
            TransferMoney r2 = new TransferMoney();
            r1.flag = 1;
            r2.flag = 0;
            Thread t1 = new Thread(r1);
            Thread t2 = new Thread(r2);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("a的余额" + a.balance);
            System.out.println("b的余额" + b.balance);
        }@Override
        public void run() {
         
            if (flag == 1) {
         
                transferMoney(a, b, 200);
            }
            if (flag == 0) {
         
                transferMoney(b, a, 200);
            }
        }public static void transferMoney(Account from, Account to, int amount) {
         
           synchronized (from){
         
               /**加上sleep就造成死锁了
               try {
                   Thread.sleep(500);      
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               */  
               synchronized (to){
         
                   if (from.balance - amount < 0){
         
                       System.out.println("余额不足,转账失败。");
                   }
                   from.balance -= amount;
                   to.balance += amount;
                   System.out.println("成功转账" + amount + "元");
               }
           }
    ​
    ​
        }static class Account {
         public Account(int balance) {
         
                this.balance = balance;
            }int balance;}
    }
    

    未加注释代码部分,运行结果如下

image-20220211175040852

加上注释后运行结果如下,由于请求锁的顺序相反,造成相互等待,互不退让,造成死锁。

image-20220212192417768

2.3 模拟多人随机转账

  • 5万人很多,但是依然会发生死锁,墨菲定律
  • 复习:发生死锁几率不高但危害大
import java.util.Random;/**
 * 描述:     多人同时转账,依然很危险
 */
public class MultiTransferMoney {
   private static final int NUM_ACCOUNTS = 500;
    private static final int NUM_MONEY = 1000;
    private static final int NUM_ITERATIONS = 1000000;
    private static final int NUM_THREADS = 20;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值