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

2023-10-20 12:48:33 浏览数 (2)

产生背景

简单的说:线程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

0 人点赞