死锁的3种死法(死锁的3种死法图解)

  本篇文章为你整理了死锁的3种死法(死锁的3种死法图解)的详细内容,包含有死锁的3种死法图片 死锁的3种死法图解 死锁的三种处理方法 死锁有哪几种 死锁的3种死法,希望能帮助你了解 死锁的3种死法。

  1. 什么是死锁

  在多线程环境中,多个进程可以竞争有限数量的资源。当一个进程申请资源时,如果这时没有可用资源,那么这个进程进入等待状态。有时,如果所申请的资源被其他等待进程占有,那么该等待进程有可能再也无法改变状态。这种情况称为死锁

  在Java中使用多线程,就会有可能导致死锁问题。死锁会让程序一直卡住,不再往下执行。我们只能通过中止并重启的方式来让程序重新执行。

  2. 造成死锁的原因

  当前线程拥有其他线程需要的资源

  当前线程等待其他线程已拥有的资源

  都不放弃自己拥有的资源

  3. 死锁的必要条件

  3.1 互斥

  进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

  3.2 不可剥夺

  进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

  3.3 请求与保持

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

  3.4 循环等待

  是指进程发生死锁后,必然存在一个进程–资源之间的环形链,通俗讲就是你等我的资源,我等你的资源,大家一直等。

  4. 死锁的分类

  4.1 静态顺序型死锁

  线程之间形成相互等待资源的环时,就会形成顺序死锁lock-ordering deadlock,多个线程试图以不同的顺序来获取相同的锁时,容易形成顺序死锁,如果所有线程以固定的顺序来获取锁,就不会出现顺序死锁问题

  经典案例是LeftRightDeadlock,两个方法,分别是leftRigth、rightLeft。如果一个线程调用leftRight,另一个线程调用rightLeft,且两个线程是交替执行的,就会发生死锁。

  

public class LeftRightDeadLock {

 

   //左边锁

   private static Object left = new Object();

   //右边锁

   private static Object right = new Object();

   * 现持有左边的锁,然后获取右边的锁

   public static void leftRigth() {

   synchronized (left) {

   System.out.println("leftRigth: left lock,threadId:" + Thread.currentThread().getId());

   //休眠增加死锁产生的概率

   sleep(100);

   synchronized (right) {

   System.out.println("leftRigth: right lock,threadId:" + Thread.currentThread().getId());

   * 现持有右边的锁,然后获取左边的锁

   public static void rightLeft() {

   synchronized (right) {

   System.out.println("rightLeft: right lock,threadId:" + Thread.currentThread().getId());

   //休眠增加死锁产生的概率

   sleep(100);

   synchronized (left) {

   System.out.println("rightLeft: left lock,threadId:" + Thread.currentThread().getId());

   * 休眠

   * @param time

   private static void sleep(long time) {

   try {

   Thread.sleep(time);

   } catch (InterruptedException e) {

   e.printStackTrace();

   public static void main(String[] args) {

   //创建一个线程池

   ExecutorService executorService = Executors.newFixedThreadPool(10);

   executorService.execute(() - leftRigth());

   executorService.execute(() - rightLeft());

   executorService.shutdown();

  

 

  输出:

  

leftRigth: left lock,threadId:12

 

  rightLeft: right lock,threadId:13

  

 

  我们发现,12号线程锁住了左边要向右边获取锁,13号锁住了右边,要向左边获取锁,因为两边都不释放自己的锁,互不相让,就产生了死锁。

  4.1.1 解决方案

  固定加锁的顺序(针对锁顺序死锁)

  只要交换下锁的顺序,让线程来了之后先获取同一把锁,获取不到就等待,等待上一个线程释放锁再获取锁。

  

public static void leftRigth() {

 

   synchronized (left) {

   synchronized (right) {

   public static void rightLeft() {

   synchronized (left) {

   synchronized (right) {

  

 

  4.2 动态锁顺序型死锁

  由于方法入参由外部传递而来,方法内部虽然对两个参数按照固定顺序进行加锁,但是由于外部传递时顺序的不可控,而产生锁顺序造成的死锁,即动态锁顺序死锁。

  上例告诉我们,交替的获取锁会导致死锁,且锁是固定的。有时候锁的执行顺序并不那么清晰,参数导致不同的执行顺序。经典案例是银行账户转账,from账户向to账户转账,在转账之前先获取两个账户的锁,然后开始转账,如果这是to账户向from账户转账,角色互换,也会导致锁顺序死锁。

  

/**

 

   * 动态顺序型死锁

   * 转账业务

  public class TransferMoneyDeadlock {

   public static void transfer(Account from, Account to, int amount) {

   //先锁住转账的账户

   synchronized (from) {

   System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + from.name + "】账户锁成功");

   //休眠增加死锁产生的概率

   sleep(100);

   //在锁住目标账户

   synchronized (to) {

   System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + to.name + "】账户锁成功");

   if (from.balance amount) {

   System.out.println("余额不足");

   return;

   } else {

   from.debit(amount);

   to.credit(amount);

   System.out.println("线程【" + Thread.currentThread().getId() + "】从【" + from.name + "】账户转账到【" + to.name + "】账户【" + amount + "】元钱成功");

   private static class Account {

   String name;

   int balance;

   public Account(String name, int balance) {

   this.name = name;

   this.balance = balance;

   void debit(int amount) {

   this.balance = balance - amount;

   void credit(int amount) {

   this.balance = balance + amount;

  
 

 

  然后就没有然后了,产生了死锁,我们发现 因为对象的调用关系,产生了互相锁住资源的问题。

  4.2.1 解决方案

  根据传入对象的hashCode硬性确定加锁顺序,消除可变性,避免死锁。

  

package com.test.thread.deadlock;

 

  import java.util.concurrent.ExecutorService;

  import java.util.concurrent.Executors;

   * 动态顺序型死锁解决方案

  public class TransferMoneyDeadlock {

   * 监视器,第三把锁,为了方式HASH冲突

   private static Object lock = new Object();

   * 我们经过上一次得失败,明白了不能依赖参数名称简单的确定锁的顺序,因为参数是

   * 具有动态性的,所以,我们改变一下思路,直接根据传入对象的hashCode()大小来

   * 对锁定顺序进行排序(这里要明白的是如何排序不是关键,有序才是关键)。

   * @param from

   * @param to

   * @param amount

   public static void transfer(Account from, Account to, int amount) {

   * 这里需要说明一下为什么不使用HashCode()因为HashCode方法可以被重写,

   * 所以,我们无法简单的使用父类或者当前类提供的简单的hashCode()方法,

   * 所以,我们就使用系统提供的identityHashCode()方法,该方法保证无论

   * 你是否重写了hashCode方法,都会在虚拟机层面上调用一个名为JVM_IHashCode

   * 的方法来根据对象的存储地址来获取该对象的hashCode(),HashCode如果不重写

   * 的话,其实也是通过这个虚拟机层面上的方法,JVM_IHashCode()方法实现的

   * 这个方法是用C++实现的。

   int fromHash = System.identityHashCode(from);

   int toHash = System.identityHashCode(to);

   if (fromHash toHash) {

   //先锁住转账的账户

   synchronized (from) {

   System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + from.name + "】账户锁成功");

   //休眠增加死锁产生的概率

   sleep(100);

   //在锁住目标账户

   synchronized (to) {

   System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + to.name + "】账户锁成功");

   if (from.balance amount) {

   System.out.println("余额不足");

   return;

   } else {

   from.debit(amount);

   to.credit(amount);

   System.out.println("线程【" + Thread.currentThread().getId() + "】从【" + from.name + "】账户转账到【" + to.name + "】账户【" + amount + "】元钱成功");

   } else if (fromHash toHash) {

   //先锁住转账的账户

   synchronized (to) {

   System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + from.name + "】账户锁成功");

   //休眠增加死锁产生的概率

   sleep(100);

   //在锁住目标账户

   synchronized (from) {

   System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + to.name + "】账户锁成功");

   if (from.balance amount) {

   System.out.println("余额不足");

   return;

   } else {

   from.debit(amount);

   to.credit(amount);

   System.out.println("线程【" + Thread.currentThread().getId() + "】从【" + from.name + "】账户转账到【" + to.name + "】账户【" + amount + "】元钱成功");

   } else {

   //如果传入对象的Hash值相同,那就加让加第三层锁

   synchronized (lock) {

   //先锁住转账的账户

   synchronized (from) {

   System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + from.name + "】账户锁成功");

   //休眠增加死锁产生的概率

   sleep(100);

   //在锁住目标账户

   synchronized (to) {

   System.out.println("线程【" + Thread.currentThread().getId() + "】获取【" + to.name + "】账户锁成功");

   if (from.balance amount) {

   System.out.println("余额不足");

   return;

   } else {

   from.debit(amount);

   to.credit(amount);

   System.out.println("线程【" + Thread.currentThread().getId() + "】从【" + from.name + "】账户转账到【" + to.name + "】账户【" + amount + "】元钱成功");

   private static class Account {

   String name;

   int balance;

   public Account(String name, int balance) {

   this.name = name;

   this.balance = balance;

   void debit(int amount) {

   this.balance = balance - amount;

   void credit(int amount) {

   this.balance = balance + amount;

  
在协作对象之间可能存在多个锁获取的情况,但是这些获取多个锁的操作并不像在LeftRightDeadLock或transferMoney中那么明显,这两个锁并不一定必须在同一个方法中被获取。如果在持有锁时调用某个外部方法,那么这就需要警惕死锁问题,因为在这个外部方法中可能会获取其他锁,或者阻塞时间过长,导致其他线程无法及时获取当前被持有的锁。

  上述两例中,在同一个方法中获取两个锁。实际上,锁并不一定在同一方法中被获取。经典案例,如出租车调度系统。

  

/**

 

   * 协作对象间的死锁

  public class CoordinateDeadlock {

   * Taxi 类

   static class Taxi {

   private String location;

   private String destination;

   private Dispatcher dispatcher;

   public Taxi(Dispatcher dispatcher, String destination) {

   this.dispatcher = dispatcher;

   this.destination = destination;

   public synchronized String getLocation() {

   return this.location;

   * 该方法先获取Taxi的this对象锁后,然后调用Dispatcher类的方法时,又需要获取

   * Dispatcher类的this方法。

   * @param location

   public synchronized void setLocation(String location) {

   this.location = location;

   System.out.println(Thread.currentThread().getName() + " taxi set location:" + location);

   if (this.location.equals(destination)) {

   dispatcher.notifyAvailable(this);

   * 调度类

   static class Dispatcher {

   private Set Taxi taxis;

   private Set Taxi availableTaxis;

   public Dispatcher() {

   taxis = new HashSet Taxi

   availableTaxis = new HashSet Taxi

   public synchronized void notifyAvailable(Taxi taxi) {

   System.out.println(Thread.currentThread().getName() + " notifyAvailable.");

   availableTaxis.add(taxi);

   * 打印当前位置:有死锁风险

   * 持有当前锁的时候,同时调用Taxi的getLocation这个外部方法;而这个外部方法也是需要加锁的

   * reportLocation的锁的顺序与Taxi的setLocation锁的顺序完全相反

   public synchronized void reportLocation() {

   System.out.println(Thread.currentThread().getName() + " report location.");

   for (Taxi t : taxis) {

   t.getLocation();

   public void addTaxi(Taxi taxi) {

   taxis.add(taxi);

   public static void main(String[] args) {

   ExecutorService executorService = Executors.newFixedThreadPool(10);

   final Dispatcher dispatcher = new Dispatcher();

   final Taxi taxi = new Taxi(dispatcher, "软件园");

   dispatcher.addTaxi(taxi);

   //先获取dispatcher锁,然后是taxi的锁

   executorService.execute(() - dispatcher.reportLocation());

   //先获取taxi锁,然后是dispatcher的锁

   executorService.execute(() - taxi.setLocation("软件园"));

   executorService.shutdown();

  

 

  4.3.1 解决方案

  使用开放调用,开放调用指调用该方法不需要持有锁。

  开放调用,是指在调用某个方法时不需要持有锁。开放调用可以避免死锁,这种代码更容易编写。上述调度算法完全可以修改为开发调用,修改同步代码块的范围,使其仅用于保护那些涉及共享状态的操作,避免在同步代码块中执行方法调用。修改Dispatcher的reportLocation方法:

  4.3.1.1 setLocation方法

  

/**

 

   * 开放调用,不持有锁期间进行外部方法调用

   * @param location

   public void setLocation(String location) {

   synchronized (this) {

   this.location = location;

   System.out.println(Thread.currentThread().getName() + " taxi set location:" + location);

   if (this.location.equals(destination)) {

   dispatcher.notifyAvailable(this);

  

 

  4.3.1.2 reportLocation 方法

  

/**

 

   * 同步块只包含对共享状态的操作代码

   public synchronized void reportLocation() {

   System.out.println(Thread.currentThread().getName() + " report location.");

   Set Taxi taxisCopy;

   synchronized (this) {

   taxisCopy = new HashSet Taxi (taxis);

   for (Taxi t : taxisCopy) {

   t.getLocation();

  

 

  本文由传智教育博学谷教研团队发布。

  如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

  转载请注明出处!

  以上就是死锁的3种死法(死锁的3种死法图解)的详细内容,想要了解更多 死锁的3种死法的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: