Java中死锁与活锁的具体实现

作者:九七年生于初夏 时间:2023-10-29 01:48:02 

活锁与死锁

活锁

活锁同样会发生在多个相互协作的线程间,当他们为了彼此间的响应而相互礼让,使得没有一个线程能够继续前进,那么就发生了活锁。同死锁一样,发生活锁的线程无法继续执行。

相当于两个在半路相遇的人:出于礼貌他们相互礼让,避开对方的路,但是在另一条路上又相遇了。就这样,不停地一直避让下去。。。。

死锁

两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候,死锁会让你的程序挂起无法完成任务。

死锁的四个必要条件

在 Java 中使用多线程,就会有可能导致死锁问题。死锁会让程序一直住,不再程序往下执行。我们只能通过中止并重启的方式来让程序重新执行。这是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生!

互斥条件

指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用完释放。

请求和保持条件

指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

不剥夺条件

指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

环路等待条件

指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{A,B,C,···,Z} 中的A正在等待一个B占用的资源;B正在等待C占用的资源,……,Z正在等待已被A占用的资源。

死锁示例

/** @author Strive */
@SuppressWarnings("all")
public class DeadLock implements Runnable {
 public int flag = 1;

/** 面包 */
 private static final Object bread = new Object();

/** 水 */
 private static final Object water = new Object();

@Override
 public void run() {
   if (flag == 1) {
     // 先吃面包再喝水,环路等待条件
     synchronized (bread) {
       try {
         Thread.sleep(500);
       } catch (Exception e) {
         e.printStackTrace();
       }
       System.out.println("面包吃了,等喝水。。。");
       synchronized (water) {
         System.out.println("面包吃了,水也喝到了");
       }
     }
   }
   if (flag == 0) {
     // 先喝水再吃面包,环路等待条件
     synchronized (water) {
       try {
         Thread.sleep(500);
       } catch (Exception e) {
         e.printStackTrace();
       }
       System.out.println("喝完水,等吃面包了。。。");
       synchronized (bread) {
         System.out.println("喝完水,面包也吃完了。。。");
       }
     }
   }
 }

public static void main(String[] args) {
   DeadLock lockBread = new DeadLock();
   DeadLock lockWater = new DeadLock();
   lockBread.flag = 1;
   lockWater.flag = 0;
   // lockBread,lockWater 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。
   // lockWater 的 run() 可能在 lockBread 的 run() 之前运行
   new Thread(lockBread).start();
   new Thread(lockWater).start();
 }
}

程序运行结果:

喝完水,等吃面包了。。。
面包吃了,等喝水。。。

死锁排查

先执行上面的代码,再进行排查!

1、使用 jps -l

D:\workspace-code\gitee\dolphin>jps -l
7072 org.jetbrains.jps.cmdline.Launcher
8824 sun.tools.jps.Jps
17212
4012 com.dolphin.thread.locks.DeadLock
6684 org.jetbrains.idea.maven.server.RemoteMavenServer36

4012 com.dolphin.thread.locks.DeadLock,粘贴进程号 4012

2、使用 jstack 4012

... ...
Java stack information for the threads listed above:
===================================================
"Thread-1":
       at com.dolphin.thread.locks.DeadLock.run(DeadLock.java:40)
       - waiting to lock <0x000000076bf9e3d8> (a java.lang.Object)
       - locked <0x000000076bf9e3e8> (a java.lang.Object)
       at java.lang.Thread.run(Thread.java:748)
"Thread-0":
       at com.dolphin.thread.locks.DeadLock.run(DeadLock.java:26)
       - waiting to lock <0x000000076bf9e3e8> (a java.lang.Object)
       - locked <0x000000076bf9e3d8> (a java.lang.Object)
       at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

从打印信息我们可以看出:

  • Found 1 deadlock: 发现一个死锁;

  • Thread-1 locked <0x000000076bf9e3e8> waiting to lock <0x000000076bf9e3d8>:线程1,锁住了 0x000000076bf9e3e8 等待 0x000000076bf9e3d8 锁;

  • Thread-0 locked <0x000000076bf9e3d8> waiting to lock <0x000000076bf9e3e8>:线程0,锁住了 0x000000076bf9e3d8 等待 0x000000076bf9e3e8 锁;

Java中死锁与活锁的具体实现

总结一下

  • 当 DeadLock 类的对象 flag=1 时(lockBread),先锁定 bread,睡眠500毫秒;

  • 而 lockBread 在睡眠的时候另一个 flag==0 的对象(lockWater)线程启动,先锁定 water,睡眠500毫秒;

  • lockBread 睡眠结束后需要锁定 water 才能继续执行,而此时 water 已被 lockWater 锁定;

  • lockWater 睡眠结束后需要锁定 bread 才能继续执行,而此时 bread 已被 lockBread 锁定;

  • lockBread、lockWater 相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁;

如何避免死锁

预防死锁

其实就是破坏死锁的四个必要条件!!!

  • 破坏互斥条件:使资源同时访问而非互斥使用,就没有进程会阻塞在资源上,从而不发生死锁。

  • 破坏请求和保持条件:采用静态分配的方式,静态分配的方式是指进程必须在执行之前就申请需要的全部资源,且直至所要的资源全部得到满足后才开始执行,只要有一个资源得不到分配,也不给这个进程分配其他的资源。

  • 破坏不剥夺条件:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源,但是只适用于内存和处理器资源。

  • 破坏循环等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行。

设置加锁顺序

两个线程试图以不同的顺序来获得相同的锁,如果按照相同的顺序来请求锁,那么就不会出现循环的加锁依赖性,因此也就不会产生死锁,每个需要锁面包和锁水的线程都以相同的顺序来获取面包和水,那么就不会发生死锁了,如下图所示:

Java中死锁与活锁的具体实现

根据上图我们修改一下之前写的死锁代码,消除死锁!

@Override
public void run() {
   if (flag == 1) {
       // 先吃面包再喝水,环路等待条件
       synchronized (bread) {
           try {
               Thread.sleep(500);
           } catch (Exception e) {
               e.printStackTrace();
           }
           System.out.println("面包吃了,等喝水。。。");
       }

synchronized (water) {
           System.out.println("面包吃了,水也喝到了");
       }
   }
   if (flag == 0) {
       // 先喝水再吃面包,环路等待条件
       synchronized (water) {
           try {
               Thread.sleep(500);
           } catch (Exception e) {
               e.printStackTrace();
           }
           System.out.println("喝完水,等吃面包了。。。");
       }

synchronized (bread) {
           System.out.println("喝完水,面包也吃完了。。。");
       }
   }
}

程序运行结果:

喝完水,等吃面包了。。。
面包吃了,等喝水。。。
面包吃了,水也喝到了
喝完水,面包也吃完了。。。

这样就可以消除死锁发生~~~

活锁示例

import java.lang.management.ManagementFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/** @author Strive */
public class LiveLock {
 static ReentrantLock bread = new ReentrantLock();
 static ReentrantLock water = new ReentrantLock();

public static void main(String[] args) {
   String name = ManagementFactory.getRuntimeMXBean().getName();
   String pid = name.split("@")[0];
   System.out.println("Pid is:" + pid);
   System.out.println("jstack " + pid);

ExecutorService executorService = Executors.newCachedThreadPool();

executorService.submit(
       () -> {
         try {
           // 尝试获得 bread 锁
           while (bread.tryLock(10, TimeUnit.SECONDS)) {
             System.out.println("拿到面包了,等着拿水。。。");
             TimeUnit.SECONDS.sleep(1);

// 判断 water 是否被锁住,如果锁住,退出!再次尝试获取 water 锁
             boolean locked = water.isLocked();
             if (locked) {
               bread.unlock();
               continue;
             }

// 尝试获得 water 锁
             boolean w = water.tryLock(10, TimeUnit.SECONDS);
             // 如果没有获取到 water 锁,释放 bread 锁,再次尝试!
             if (!w) {
               bread.unlock();
               continue;
             }
             System.out.println("拿到水了");
             break;
           }
           System.out.println("吃面包,喝水!");
         } catch (InterruptedException e) {
           System.out.println("线程中断");
         } finally {
           water.unlock();
           bread.unlock();
         }
       });

executorService.submit(
       () -> {
         try {
           while (water.tryLock(10, TimeUnit.SECONDS)) {
             System.out.println("拿到水了,等着拿面包。。。");
             TimeUnit.SECONDS.sleep(2);

// 判断 bread 是否被锁住,如果锁住,退出!再次尝试获取 bread 锁
             boolean locked = bread.isLocked();
             if (locked) {
               water.unlock();
               continue;
             }

// 尝试获得 bread 锁
             boolean b = bread.tryLock(10, TimeUnit.SECONDS);
             // 如果没有获取到 bread 锁,释放 water 锁,再次尝试!
             if (!b) {
               water.unlock();
               continue;
             }
             System.out.println("拿到面包了");
             break;
           }
           System.out.println("喝水,吃面包!");
         } catch (InterruptedException e) {
           System.out.println("线程中断");
         } finally {
           bread.unlock();
           water.unlock();
         }
       });
   executorService.shutdown();
 }
}

程序运行结果:

Pid is:8788
jstack 8788
拿到面包了,等着拿水。。。
拿到水了,等着拿面包。。。
拿到面包了,等着拿水。。。
拿到水了,等着拿面包。。。
拿到面包了,等着拿水。。。
拿到面包了,等着拿水。。。
拿到水了,等着拿面包。。。

已经输出打印了 Pid,尝试自己用 jstack 调试一下吧!可以参考如何排查死锁的章节~

解决活锁

锁让出的时候添加随机睡眠时间

修改上面的程序,参考下面的代码:

executorService.submit(
       () -> {
         try {
           // 尝试获得 bread 锁
           while (bread.tryLock(10, TimeUnit.SECONDS)) {
             System.out.println("拿到面包了,等着拿水。。。");
             TimeUnit.SECONDS.sleep(1);

// 判断 water 是否被锁住,如果锁住,退出!再次尝试获取 water 锁
             boolean locked = water.isLocked();
             if (locked) {
               bread.unlock();
               // 避免活锁
               TimeUnit.MILLISECONDS.sleep(1000);
               continue;
             }

// 尝试获得 water 锁
             boolean w = water.tryLock(10, TimeUnit.SECONDS);
             // 如果没有获取到 water 锁,释放 bread 锁,再次尝试!
             if (!w) {
               bread.unlock();
               continue;
             }
             System.out.println("拿到水了");
             break;
           }
           System.out.println("吃面包,喝水!");
         } catch (InterruptedException e) {
           System.out.println("线程中断");
         } finally {
           water.unlock();
           bread.unlock();
         }
       });

executorService.submit(
   () -> {
       try {
           while (water.tryLock(10, TimeUnit.SECONDS)) {
               System.out.println("拿到水了,等着拿面包。。。");
               TimeUnit.SECONDS.sleep(2);

// 判断 bread 是否被锁住,如果锁住,退出!再次尝试获取 bread 锁
               boolean locked = bread.isLocked();
               if (locked) {
                   water.unlock();
                   // 避免活锁
                   TimeUnit.MILLISECONDS.sleep(1500);
                   continue;
               }

// 尝试获得 bread 锁
               boolean b = bread.tryLock(10, TimeUnit.SECONDS);
               // 如果没有获取到 bread 锁,释放 water 锁,再次尝试!
               if (!b) {
                   water.unlock();
                   continue;
               }
               System.out.println("拿到面包了");
               break;
           }
           System.out.println("喝水,吃面包!");
       } catch (InterruptedException e) {
           System.out.println("线程中断");
       } finally {
           bread.unlock();
           water.unlock();
       }
   });
executorService.shutdown();

程序输出结果:

Pid is:18256
jstack 18256
拿到面包了,等着拿水。。。
拿到水了,等着拿面包。。。
拿到面包了
喝水,吃面包!
拿到面包了,等着拿水。。。
拿到水了
吃面包,喝水!

Process finished with exit code 0

来源:https://juejin.cn/post/7048957371611086861

标签:Java,死锁,活锁
0
投稿

猜你喜欢

  • 详解Http请求中Content-Type讲解以及在Spring MVC中的应用

    2022-10-19 04:14:11
  • MyBatis动态SQL如何实现前端指定返回字段

    2023-11-28 23:00:58
  • Java多线程之ThreadLocal浅析

    2023-06-19 19:55:37
  • Flutter Widgets MediaQuery控件屏幕信息适配

    2023-06-29 04:48:21
  • 深入浅析Spring 的aop实现原理

    2023-01-10 00:00:10
  • C#子线程执行完后通知主线程的方法

    2022-02-26 20:15:40
  • 详解JavaFX桌面应用开发-Group(容器组)

    2023-12-14 22:22:38
  • springboot 整合 sa-token简介及入门教程

    2023-03-24 01:10:45
  • Android利用Badge组件实现未读消息小红点

    2021-11-09 10:30:33
  • springcloud feign传输List的坑及解决

    2023-06-20 18:31:57
  • Android开发入门环境快速搭建实战教程

    2022-06-17 15:59:41
  • 详解利用Spring加载Properties配置文件

    2023-04-04 20:53:13
  • 基于Hibernate中配置文件的学习(分享)

    2022-02-11 12:03:31
  • C#中的静态成员、静态方法、静态类介绍

    2022-04-25 20:06:51
  • SpringBoot MainApplication类文件的位置详解

    2023-10-28 16:21:12
  • 浅谈Android轻量级的数据缓存框架RxCache

    2023-12-22 14:01:30
  • Android开发Jetpack组件LiveData使用讲解

    2023-03-21 09:27:49
  • SpringBoot中的multipartResolver上传文件配置

    2022-01-22 11:06:51
  • spring cloud整合ribbon问题及解决方案

    2023-07-25 04:24:49
  • C#实现语音播报功能

    2023-06-26 20:35:26
  • asp之家 软件编程 m.aspxhome.com