前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >产生线程死锁的原因和处理方式

产生线程死锁的原因和处理方式

作者头像
潇洒
发布2023-10-20 12:48:33
3870
发布2023-10-20 12:48:33
举报
文章被收录于专栏:石头岛

产生背景

简单的说:线程1 想要去拿一个由 线程2 持有的锁,由于synchronized 的锁是互斥锁,某一时刻只能被一个线程所持有,所以线程1 就拿不到锁。

死锁原因

是指两个两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。 原因如下:

  1. 因为系统资源不足。
  2. 进程运行推进的顺序不合适,这种产生的最多。
  3. 资源分配不当。

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

因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。

产生背景: 在多线程环境下,争抢同是争抢对方资源(锁)就会产生该问题,即产生死锁。

java 死锁产生的四个必要条件

  1. 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  2. 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  3. 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  4. 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

举列说明场景

代码语言:javascript
复制
/**
 * 死锁
 */
public class SyncDeadLock {
  private static Object locka = new Object();
  private static Object lockb = new Object();

  public static void main(String[] args) {
    new SyncDeadLock().deadLock();
  }

  private void deadLock() {

    Thread thread1 = new Thread(() -> {
      synchronized (locka) {
        try {
          System.out.println(Thread.currentThread().getName() + " 拿到 lock-A!");
          Thread.sleep(500);
          System.out.println(Thread.currentThread().getName() + " 睡眠 500ms 后续继执行...!");
        } catch (Exception e) {
          e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 偿试获取 lock-B!");
        synchronized (lockb) {
          System.out.println(Thread.currentThread().getName() + " 已获得 lock-B!");
        }
      }
    }, "thread1");

    Thread thread2 = new Thread(() -> {
      synchronized (lockb) {
        try {
          System.out.println(Thread.currentThread().getName() + " 拿到 lock-B!");
          Thread.sleep(500);
          System.out.println(Thread.currentThread().getName() + " 睡眠 500ms 后续继执行...!");
        } catch (Exception e) {
          e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 偿试获取 lock-A!");
        synchronized (locka) {
          System.out.println(Thread.currentThread().getName() + " 已获得 lock-A!");
        }
      }
    }, "thread2");

    thread1.start();
    thread2.start();
  }
}

结果

代码语言:javascript
复制
thread1 拿到 lock-A!
thread2 拿到 lock-B!
thread1 睡眠 500ms 后续继执行...!
thread1 偿试获取 lock-B!
thread2 睡眠 500ms 后续继执行...!
thread2 偿试获取 lock-A!

帮助理解

地上放着两个桶泡面,一个老坛酸菜,一个小鸡炖磨茹。两个人一个产品,一个测试,同时出发去抢老坛酸菜和小鸡炖磨茹,产品老坛酸菜,测试拿到小鸡炖磨茹,同一时刻,产品伸要去拽测试怀里的小鸡炖磨茹,测试伸手去拽产品的老坛酸菜,两个僵持不下,就死在那了,叫死锁。如果没有开发将他们各打一顿解救出来,它们奖无法推进下去。

死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。

死锁预防

如果只使用一个锁就不会有死锁的问题,不过复杂场景下不太理实。

以确定的顺序获得锁

代码语言:javascript
复制
线程A ---> 锁定 A  ----> 偿试锁定 B
线程B ---> 锁定 A  ----> 偿试锁定 B
这样就不会发生死锁

超时放弃

Lock 接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。

信号量控制

代码语言:javascript
复制
import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 信号量控制,解决死锁
 */
public class UnLockTest {
   public static final Semaphore a1 = new Semaphore(1);
   public static final Semaphore a2 = new Semaphore(1);

   public static void main(String[] args) {
      LockAa la = new LockAa();
      new Thread(la).start();
      LockBb lb = new LockBb();
      new Thread(lb).start();
   }
}
class LockAa implements Runnable {
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockA 开始执行");
         while (true) {
            if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
               System.out.println(new Date().toString() + " LockA 锁住 obj1");
               if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
                  System.out.println(new Date().toString() + " LockA 锁住 obj2");
                  Thread.sleep(60 * 1000); // do something
               }else{
                  System.out.println(new Date().toString() + "LockA 锁 obj2 失败");
               }
            }else{
               System.out.println(new Date().toString() + "LockA 锁 obj1 失败");
            }
            UnLockTest.a1.release(); // 释放
            UnLockTest.a2.release();
            Thread.sleep(1000); // 马上进行尝试,现实情况下do something是不确定的
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
class LockBb implements Runnable {
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockB 开始执行");
         while (true) {
            if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
               System.out.println(new Date().toString() + " LockB 锁住 obj2");
               if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
                  System.out.println(new Date().toString() + " LockB 锁住 obj1");
                  Thread.sleep(60 * 1000); // do something
               }else{
                  System.out.println(new Date().toString() + "LockB 锁 obj1 失败");
               }
            }else{
               System.out.println(new Date().toString() + "LockB 锁 obj2 失败");
            }
            UnLockTest.a1.release(); // 释放
            UnLockTest.a2.release();
            Thread.sleep(10 * 1000); // 这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
代码语言:javascript
复制
Sun Oct 10 12:15:25 CST 2018 LockA 锁住 obj1
Sun Oct 10 12:15:25 CST 2018 LockA 锁住 obj2
Sun Oct 10 12:15:43 CST 2018 LockB 锁住 obj2
Sun Oct 10 12:15:43 CST 2018 LockB 锁住 obj1
Sun Oct 10 12:16:26 CST 2018 LockA 锁住 obj1
Sun Oct 10 12:16:26 CST 2018 LockA 锁住 obj2
Sun Oct 10 12:16:53 CST 2018 LockB 锁住 obj2
Sun Oct 10 12:16:53 CST 2018 LockB 锁住 obj1
Sun Oct 10 12:17:27 CST 2018 LockA 锁住 obj1
Sun Oct 10 12:17:27 CST 2018 LockA 锁住 obj2
Sun Oct 10 12:18:03 CST 2018 LockB 锁住 obj2
Sun Oct 10 12:18:03 CST 2018 LockB 锁住 obj1
Sun Oct 10 12:18:28 CST 2018 LockA 锁住 obj1
Sun Oct 10 12:18:28 CST 2018 LockA 锁住 obj2
Sun Oct 10 12:19:13 CST 2018 LockB 锁住 obj2
Sun Oct 10 12:19:13 CST 2018 LockB 锁住 obj1
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-04-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 产生背景
  • 死锁原因
    • java 死锁产生的四个必要条件
      • 举列说明场景
        • 帮助理解
        • 死锁预防
          • 以确定的顺序获得锁
            • 超时放弃
              • 信号量控制
              相关产品与服务
              腾讯云服务器利旧
              云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档