java死锁解决_Java程序死锁问题定位与解决

一、概述

死锁是指两个或两个以上的进程在执行过程中,因争抢资源而造成的一种互相等待的现象,若无外力干涉它们将无法推进,如果系统资源充足,进程的资源请求能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

6dd87d1e26059687615bd532c95536f0.pngwAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

死锁产生的原因:【1】系统资源不足;【2】资源分配不当;【3】进程运行推进的顺序不合适;

形成死锁的四个必要条件:

【1】互斥条件:一个资源每次只能被一个进程使用。

【2】请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

【3】不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

【4】循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

二、代码演示

1 public classTestMian {2 //A、B 表示两把锁

3 String A = "A";4 String B = "B";5 public static voidmain(String[] args) {6 TestMian testMian = newTestMian();7 new Thread(()->{8 try{9 testMian.a();10 } catch(InterruptedException e) {11 e.printStackTrace();12 }13 }).start();14

15 new Thread(()->{16 try{17 testMian.b();18 } catch(InterruptedException e) {19 e.printStackTrace();20 }21 }).start();22 }23

24

25 public void a() throwsInterruptedException {26 //持有锁A后,尝试持有锁B ***********重点**************

27 synchronized(A){28 System.out.println("A");29 TimeUnit.SECONDS.sleep(1);30 synchronized(B){31 System.out.println("B");32 }33 }34 }35

36 public void b() throwsInterruptedException {37 //持有锁B后,尝试持有锁A ***********重点**************

38 synchronized(B){39 System.out.println("B");40 TimeUnit.SECONDS.sleep(1);41 synchronized(A){42 System.out.println("A");43 }44 }45 }46 }

三、排查死锁

【1】jps 命令定位进程号:window 下 java 运行程序,也有类似与 Linux 操作系统的 ps -ef|grep xxx 的查看进程的命令,我们这里只查看 java 的进程,即使用 jps 命令

0c9df338fa00b678d177a76f444a5deb.pngwAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

【2】jstack 能够找到死锁信息:

34dc77773a13f5fae00f5eceae17b16b.pngwAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

四、如何避免线程死锁

【1】破坏互斥条件:这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。

【2】破坏请求与保持条件:一次性申请所有的资源。

【3】破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。

【4】破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

避免死锁可以概括成三种方法:

【1】固定加锁的顺序(针对锁顺序死锁);

【2】开放调用(针对对象之间协作造成的死锁);

【3】使用定时锁 tryLock();使用显式 Lock锁,在获取锁时使用 tryLock()方法。当等待超过时限的时候,tryLock()不会一直等待,而是返回错误信息。使用tryLock()能够有效避免死锁问题。

如果等待获取锁时间超时,则抛出异常而不是一直等待!

五、死锁案例及解决方案

【1】不同的加锁顺序案例:

1 public classDiffLockOrder {2

3 private intamount;4

5 public DiffLockOrder(intamount){6 this.amount=amount;7 }8

9 //第一个锁 this 是 DiffLockOrder的对象,第二个锁target 也是 DiffLockOrder对象,为死锁埋下了隐患

10 public void transfer(DiffLockOrder target,inttransferAmount){11 synchronized (this){12 synchronized(target){13 if(amount

【2】 上面的例子中,我们模拟一个转账的过程,amount用来表示用户余额。transfer用来将当前账号的一部分金额转移到目标对象中。为了保证在 transfer的过程中,两个账户不被别人修改,我们使用了两个synchronized 关键字,分别把 transfer对象和目标对象进行锁定。看起来好像没问题,但是我们没有考虑在调用的过程中,transfer的顺序是可以发送变化的:

1 DiffLockOrder account1 = new DiffLockOrder(1000);2 DiffLockOrder account2 = new DiffLockOrder(500);3

4 Runnable target1= ()->account1.transfer(account2,200);5 Runnable target2= ()->account2.transfer(account1,100);6 newThread(target1).start();7 new Thread(target2).start();

上面的例子中,我们定义了两个account,然后两个账户互相转账,最后很有可能导致互相锁定,最后产生死锁。

解决方案一:使用 private类变量,只是用一个 sync就可以在所有的实例中同步,来解决两个 sync顺序问题。因为类变量是在所有实例中共享的,这样一次sync就够了:

1 public classLockWithPrivateStatic {2

3 private intamount;4 //不管有多少个实例,共享同一个 lock

5 private static final Object lock = newObject();6

7 public LockWithPrivateStatic(intamount){8 this.amount=amount;9 }10

11 public void transfer(LockWithPrivateStatic target, inttransferAmount){12 synchronized(lock) {13 if (amount

解决方案二:使用相同的Order,我们产生死锁的原因是无法控制上锁的顺序,如果我们能够控制上锁的顺序,是不是就不会产生死锁了呢?带着这个思路,我们给对象再加上一个 id字段:

1 private final long id; //唯一ID,用来排序

2 private static final AtomicLong nextID = new AtomicLong(0); //用来生成ID

3

4 public DiffLockWithOrder(intamount){5 this.amount=amount;6 this.id =nextID.getAndIncrement();7 }

在初始化对象的时候,我们使用 static的 AtomicLong类来为每个对象生成唯一的ID。在做 transfer的时候,我们先比较两个对象的ID大小,然后根据 ID进行排序,最后安装顺序进行加锁。这样就能够保证顺序,从而避免死锁。

1 public void transfer(DiffLockWithOrder target, inttransferAmount){2 //将加锁的对象修改为可变参数,ID小的永远为第一个锁对象

3 DiffLockWithOrder fist, second;4

5 if (compareTo(target) < 0) {6 fist = this;7 second =target;8 } else{9 fist =target;10 second = this;11 }12

13 synchronized(fist){14 synchronized(second){15 if(amount

解决方案三:释放掉已占有的锁,死锁是互相请求对方占用的锁,但是对方的锁一直没有释放,我们考虑一下,如果获取不到锁的时候,自动释放已占用的锁是不是也可以解决死锁的问题呢?因为 ReentrantLock有一个 tryLock()方法,我们可以使用这个方法来判断是否能够获取到锁,获取不到就释放已占有的锁。我们使用 ReentrantLock来完成这个例子:

1 public classDiffLockWithReentrantLock {2

3 private intamount;4 private final Lock lock = newReentrantLock();5

6 public DiffLockWithReentrantLock(intamount){7 this.amount=amount;8 }9

10 private void transfer(DiffLockWithReentrantLock target, inttransferAmount)11 throwsInterruptedException {12 while (true) {13 if (this.lock.tryLock()) {14 try{15 if(target.lock.tryLock()) {16 try{17 if(amount

33 Thread.sleep(1000+new Random(1000L).nextInt(1000));34 }35 }36

37 }

我们把两个 tryLock方法在 while循环中,如果不能获取到锁就循环遍历。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值