java死锁定位_Java并发编程入门(九)死锁和死锁定位

65d65abcedc45954d9221779726d28a0.png

Java极客  |  作者  /  铿然一叶

这是Java极客的第 37 篇原创文章

相关阅读:

一、死锁条件

死锁:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。

满足死锁的四个条件:

1.互斥,共享资源 X 和 Y 只能被一个线程占用

2.占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源Y的时候,不释放共享资源 X;

3.不可抢占,其他线程不能强行抢占线程 T1占有的资源,因为不可抢占,所以要等待;

4.循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环等待。

这四个条件同时满足时,才会发生死锁,因此避免死锁只要打破其中一个条件则可。

二、避免死锁方法

1.对于互斥这个条件无法破坏,因为使用锁为的就是互斥。

2.对于占有且等待,可以同时获取要使用的多个资源锁X和Y,这样就不会存在取得了X还要等待Y。这种方式只在需要获取的资源锁较少的情况下使用,如果要获取的资源锁很多(例如10个),就不太可行。

3.对于不可抢占,可以获取了部分资源,再进一步获取其他资源时如果获取不到时,把已经获取的资源一起释放掉。此时意味着操作不能按照预期处理,需要考虑异常如何处理,例如是否需要重试。

4.对于循环等待,可以将需要获取的锁资源排序,按照顺序获取,这样就不会多个线程交叉获取相同的资源导致死锁,而是在获取相同的资源时就等待,直到它释放。

综上,对于极易发生死锁的场景,处理如下:

1.获取锁时带上超时时间,获取不到就放弃,这样能最简单的避免死锁,这也意味着不能使用synchronized关键字来获得锁资源。

2.对于已经获取到的锁资源,增加主动释放机制。

3.放弃锁资源时增加异常流程处理,如重试。

4.需要获取的多个锁资源排序处理,虽然前面几点可以一定程度避免死锁,但不排序的结果就是首次处理失败,重试时还可能再次失败,虽然没有发生死锁,但同一笔业务重试了N次可能也没有成功,导致无谓占用资源。

三、死锁定位

1.模拟死锁代码

package com.javashizhan.concurrent.demo.deadlock;

/**

* @ClassName DeadlockDemo

* @Description TODO

* @Author 铿然一叶

* @Date 2019/10/3 23:40

* javashizhan.com

**/

public class DeadlockDemo{

public static void main(String[] args){

//创建两个用于加锁的对象

final Object lockX = new Object();

final Object lockY = new Object();

System.out.println("lockX " + lockX);

System.out.println("lockY " + lockY);

Thread tX = new Thread(new Worker(lockX, lockY), "tX");

//交换锁的顺序,模拟死锁

Thread tY = new Thread(new Worker(lockY, lockX), "tY");

tX.start();

tY.start();

}

}

class Worker implements Runnable{

private final Object lockX;

private final Object lockY;

public Worker(Object lockX, Object lockY){

this.lockX = lockX;

this.lockY = lockY;

}

public void run(){

synchronized (lockX) {

//休眠一会,等待另外一个线程获取到lockY

sleep(2000);

System.out.println(Thread.currentThread().getName() + " get lock " + lockX);

synchronized (lockY) {

//这一步由于发生了死锁永远不会执行

System.out.println(Thread.currentThread().getName() + " get lock " + lockY);

}

}

}

private void sleep(long millis){

try {

Thread.sleep(millis);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

复制代码

2.执行程序输出的日志

lockX java.lang.Object@28d93b30

lockY java.lang.Object@1b6d3586

tX get lock java.lang.Object@28d93b30

tY get lock java.lang.Object@1b6d3586

复制代码

可以看到两个线程各自获取一个锁后发生了死锁,没有继续往下执行。

3.jps查看java进程

4.jstack查看java进程堆栈信息,关键部分如下:

a0bf499089a9f31faf240e2bd71079a9.png

可以看到有一个死锁发生,原因是tY线程和tX线程已经获取到的锁和将要获取的锁形成了循环依赖,导致死锁。

四、解决死锁问题

对于这个例子,死锁是因为两个锁循环依赖,根据上面描述的避免死锁方法,只要对锁排序则可,排序代码如下:

public Worker(Object lockX, Object lockY){

int result = lockX.toString().compareTo(lockY.toString());

this.lockX = result == -1 ? lockX : lockY;

this.lockY = result == -1 ? lockY : lockX;

}

复制代码

代码修改后程序执行日志:

lockX java.lang.Object@28d93b30

lockY java.lang.Object@1b6d3586

tX get lock java.lang.Object@1b6d3586

tX get lock java.lang.Object@28d93b30

tY get lock java.lang.Object@1b6d3586

tY get lock java.lang.Object@28d93b30

复制代码

可以看到锁排序后,只有一个线程获取到所有锁并执行完后,另外一个线程才能获取锁,死锁问题解决。

end.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值