更多内容,前往 IT-BLOG

一、概述


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

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

形成死锁的四个必要条件:
【1】互斥条件:一个资源每次只能被一个进程使用。
【2】请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
【3】不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
【4】循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

二、代码演示


 1 public class TestMian {
2 //A、B 表示两把锁
3 String A = "A";
4 String B = "B";
5 public static void main(String[] args) {
6 TestMian testMian = new TestMian();
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() throws InterruptedException {
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() throws InterruptedException {
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 命令

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

四、如何避免线程死锁


【1】破坏互斥条件:这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
【2】破坏请求与保持条件:一次性申请所有的资源。
【3】破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
【4】破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

避免死锁可以概括成三种方法:
【1】固定加锁的顺序(针对锁顺序死锁);
【2】开放调用(针对对象之间协作造成的死锁);
【3】使用定时锁 tryLock();使用显式 Lock锁,在获取锁时使用 tryLock()方法。当等待超过时限的时候,tryLock()不会一直等待,而是返回错误信息。使用tryLock()能够有效避免死锁问题。

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

五、死锁案例及解决方案


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

 1 public class DiffLockOrder {
2
3 private int amount;
4
5 public DiffLockOrder(int amount){
6 this.amount=amount;
7 }
8
9 // 第一个锁 this 是 DiffLockOrder的对象,第二个锁target 也是 DiffLockOrder对象,为死锁埋下了隐患
10 public void transfer(DiffLockOrder target,int transferAmount){
11 synchronized (this){
12 synchronized (target){
13 if(amount< transferAmount){
14 System.out.println("余额不足!");
15 }else{
16 amount=amount-transferAmount;
17 target.amount=target.amount+transferAmount;
18 }
19 }
20 }
21 }
22 }

【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 new Thread(target1).start();
7 new Thread(target2).start();

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

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

 1 public class LockWithPrivateStatic {
2
3 private int amount;
4 // 不管有多少个实例,共享同一个 lock
5 private static final Object lock = new Object();
6
7 public LockWithPrivateStatic(int amount){
8 this.amount=amount;
9 }
10
11 public void transfer(LockWithPrivateStatic target, int transferAmount){
12 synchronized (lock) {
13 if (amount < transferAmount) {
14 System.out.println("余额不足!");
15 } else {
16 amount = amount - transferAmount;
17 target.amount = target.amount + transferAmount;
18 }
19 }
20 }
21 }

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

1 private final long id; // 唯一ID,用来排序
2 private static final AtomicLong nextID = new AtomicLong(0); // 用来生成ID
3
4 public DiffLockWithOrder(int amount){
5 this.amount=amount;
6 this.id = nextID.getAndIncrement();
7 }

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

 1 public void transfer(DiffLockWithOrder target, int transferAmount){
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< transferAmount){
16 System.out.println("余额不足!");
17 }else{
18 amount=amount-transferAmount;
19 target.amount=target.amount+transferAmount;
20 }
21 }
22 }
23 }

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

 1 public class DiffLockWithReentrantLock {
2
3 private int amount;
4 private final Lock lock = new ReentrantLock();
5
6 public DiffLockWithReentrantLock(int amount){
7 this.amount=amount;
8 }
9
10 private void transfer(DiffLockWithReentrantLock target, int transferAmount)
11 throws InterruptedException {
12 while (true) {
13 if (this.lock.tryLock()) {
14 try {
15 if (target.lock.tryLock()) {
16 try {
17 if(amount< transferAmount){
18 System.out.println("余额不足!");
19 }else{
20 amount=amount-transferAmount;
21 target.amount=target.amount+transferAmount;
22 }
23 break;
24 } finally {
25 target.lock.unlock();
26 }
27 }
28 } finally {
29 this.lock.unlock();
30 }
31 }
32 //随机sleep一定的时间,保证可以释放掉锁
33 Thread.sleep(1000+new Random(1000L).nextInt(1000));
34 }
35 }
36
37 }

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

Java程序死锁问题定位与解决的更多相关文章

  1. java程序死锁,3种方式快速找到死锁代码

    java程序中出现死锁问题,如果不了解排查方法,是束手无策的,今天咱们用三种方法找到死锁问题. 运行下面代码 package com.jvm.visualvm; /** * <a href=&q ...

  2. 第一个java程序中文乱码以及如何解决

    出现问题:编码gbk的不可映射字段 原因:.java文件的编码与cmd命令执行器使用的编码不一致 我们使用的.java文件的编码为UTF-8 Cmd默认使用的编码为GBK: 解决方式统一编码: 方法1 ...

  3. Java中死锁的定位与修复

    死锁应该可以说是并发编程中比较常见的一种情况,可以说如果程序产生了死锁那将会对程序带来致命的影响:所以排查定位.修复死锁至关重要: 我们都知道死锁是由于多个对象或多个线程之间相互需要对方锁持有的锁而又 ...

  4. 【经验随笔】Java程序远程调试定位特定运行环境上出现的问题

    Java后台程序远程调试 第一步:在JVM的启动参数中增加-Xdebug -Xrunjdwp:transport=dt_socket,address=6688,server=y,suspend=n 第 ...

  5. IDEA maven 多项目 出现 java 程序包找不到

    使用idea 多项目引入的时候,出现报错信息:Error java程序包找不到 解决办法: 我在引入多个项目的时候,他们是在一个目录里面的.我把整个目录引入了进去.结果报依赖包找不到. 把引入的全部项 ...

  6. Java程序中解决数据库超时与死锁

    Java程序中解决数据库超时与死锁 2011-06-07 11:09 佚名 帮考网 字号:T | T   Java程序中解决数据库超时与死锁,每个使用关系型数据库的程序都可能遇到数据死锁或不可用的情况 ...

  7. Java:死锁编码及定位分析

    Java:死锁编码及定位分析 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 概念 死锁是指两个或多个以上的进程在执行过程中,因争夺资源而造成一种互相等待的现象, ...

  8. Java 多线程 死锁 隐性死锁 数据竞争 恶性数据竞争 错误解决深入分析 全方向举例

    在几乎所有编程语言中,由于多线程引发的错误都有着难以再现的特点,程序的死锁或其它多线程错误可能只在某些特殊的情形下才出现,或在不同的VM上运行同一个程序时错误表现不同.因此,在编写多线程程序时,事先认 ...

  9. 解决因为终端打印造成的java程序假死

    问题状态: java 程序 日志采用 log4j 运行时由另一个管理进程拉起,程序在后台运行. 现象: 程序后台运行时,运行一段时间后假死 分析原因: 尝试打印输出,定位假死的具体位置,发现出现假死的 ...

  10. java中多线程产生死锁的原因以及解决意见

    1.  java中导致死锁的原因 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,而该资源又被其他线程锁定,从而导致每一个线程都得等其它线程释放其锁定的资源,造成了所有线程都无法正常结 ...

随机推荐

  1. 微信小程序云开发,快速生成短信验证码

    使用微信小程序云函数实现注册短信验证码的管理,并不是一件分分钟的事,目前想要存储验证码只能放到数据库中,因为存储后才能和用户提交上来的验证码做比较. 管理验证码主要涉及到:生成.存储.校验.有效期管理 ...

  2. 解决java.sql.SQLException: null, message from server: "Host 'XXX' is not allowed to connect异常

    Sqoop连接MySQL报异常.这个异常是数据库只允许localhost或127.0.0.1访问,不允许远程访问.我用的本机IP都不行. 解决办法:修改访问权限即可. 打开cmd,进入mysql fl ...

  3. Dapper存储过程分页

    create database Month6use Month6 --用户表create table UserInfo( UId int primary key identity, UName var ...

  4. Java中String,JSON对象,java实体类(Bean)之间的相互转换

    FastJson对于json格式字符串的解析主要用到了一下三个类: (1)JSON:fastJson的解析器,用于JSON格式字符串与JSON对象及javaBean之间的转换. (2)JSONObje ...

  5. Jndi结合DynamicDataSource实现多数据源配置

    首先注意本框架是SSM,配置主要在两个地方.第一个是applicationContext.xml,第二个文件是Tomcat下面的context.xml里面 1.context.xml文件配置的代码如下 ...

  6. Unity C#代码入门

    Unity C#代码入门 1. 脚本基本结构 1.1 unity生成的模板 using System.Collections; using System.Collections.Generic; us ...

  7. 2月22日javaweb学习之Maven

    Maveb是专门用于管理和构建java项目的工具,它的主要功能有: 1.提供一套标准化的项目结构. 2.提供一套标准化的构建流程(编译.测试.打包.发布......) 3.提供了一套依赖管理机制 Ma ...

  8. windows系统下查找开放端口的监听程序

    C:\Program Files (x86)\Nmap>nmap 127.0.0.1Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-15 1 ...

  9. shell typeset 命令使用修改大小写

    typeset的-u选项可以将一个变量的字符变成大写 1 /home/lee#typeset -u var=abc 2 /home/lee#echo $var 3 ABC -l选项将一个变量的字符变成 ...

  10. resnet模型下载

    resnet模型下载: model_urls = { 'resnet18': 'https://s3.amazonaws.com/pytorch/models/resnet18-5c106cde.pt ...