返回介绍

4.5 有关死锁的问题

发布于 2024-08-21 22:20:21 字数 4142 浏览 0 评论 0 收藏 0

在学习了无锁之后,让我们重新回到锁的世界吧!在众多的应用程序中,使用锁的情况一般要多于无锁。因为对于应用来说,如果业务逻辑很复杂,会极大增加无锁的编程难度。但如果使用锁,我们就不得不对一个新的问题引起重视——那就是死锁。

那什么是死锁呢?通俗的说,死锁就是两个或者多个线程,相互占用对方需要的资源,而都不进行释放,导致彼此之间都相互等待对方释放资源,产生了无限制等待的现象。死锁一旦发生,如果没有外力介入,这种等待将永远存在,从而对程序产生严重的影响。

用来描述死锁问题的一个有名的场景是“哲学家就餐”问题。哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。

哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。如图4.3所示,显示了这种情况。

图4-3 哲学家就餐问题

最简单的情况就是只有两个哲学家,假设是A和B。桌面也只有两个叉子。A左手拿着其中一只叉子,B也一样。这样他们的右手等在等待对方的叉子,并且这种等待会一直持续,从而导致程序永远无法正常执行。

下面让我们用一个简单的例子来模拟这个过程:

01 public class DeadLock extends Thread {
02   protected Object tool;
03   static Object fork1 = new Object();
04   static Object fork2 = new Object();
05
06   public DeadLock(Object obj) {
07     this.tool = obj;
08     if (tool == fork1) {
09       this.setName("哲学家A");
10     }
11     if (tool == fork2) {
12       this.setName("哲学家B");
13     }
14   }
15
16   @Override
17   public void run() {
18     if (tool == fork1) {
19       synchronized (fork1) {
20         try {
21           Thread.sleep(500);
22         } catch (Exception e) {
23           e.printStackTrace();
24         }
25         synchronized (fork2) {
26           System.out.println("哲学家A开始吃饭了");
27         }
28       }
29
30     }
31     if (tool == fork2) {
32       synchronized (fork2) {
33         try {
34           Thread.sleep(500);
35         } catch (Exception e) {
36           e.printStackTrace();
37         }
38         synchronized (fork1) {
39           System.out.println("哲学家B开始吃饭了");
40         }
41       }
42
43     }
44   }
45
46   public static void main(String[] args) throws InterruptedException {
47     DeadLock 哲学家A = new DeadLock(fork1);
48     DeadLock 哲学家B = new DeadLock(fork2);
49     哲学家A.start();
50     哲学家B.start();
51     Thread.sleep(1000);
52   }
53 }

上述代码模拟了两个哲学家互相等待对方的叉子。哲学家A先占用叉子1,哲学家B占用叉子2,接着他们就相互等待,都没有办法同时获得两个叉子用餐。

如果在实际环境中,遇到了这种情况,通常的表现就是相关的进程不再工作,并且CPU占用率为0(因为死锁的线程不占用CPU),不过这种表面现象只能用来猜测问题。如果想要确认问题,还需要使用JDK提供的一套专业工具。

首先,我们可以使用jps命令得到java进程的进程ID,接着使用jstack命令得到线程的线程堆栈:

C:\Users\Administrator>jps
8404
944
3992 DeadLock
3260 Jps
//使用jstack查看进程内所有的线程堆栈
C:\Users\Administrator>jstack 3992
//省略部分输出,只列出当前与死锁有关的线程
"哲学家B" #9 prio=5 os_prio=0 tid=0x01ccf400 nid=0xb70 waiting for monitor entry [0x1597f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at geym.conc.ch4.deadlock.DeadLock.run(DeadLock.java:42)
    - waiting to lock <0x046b3430> (a java.lang.Object)
    - locked <0x046b3438> (a java.lang.Object)

"哲学家A" #8 prio=5 os_prio=0 tid=0x01ccec00 nid=0x1064 waiting for monitor entry [0x160ff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at geym.conc.ch4.deadlock.DeadLock.run(DeadLock.java:29)
    - waiting to lock <0x046b3438> (a java.lang.Object)
    - locked <0x046b3430> (a java.lang.Object)
//自动找到了一个死锁,确认死锁的存在
Found one Java-level deadlock:
=============================
"哲学家B":
  waiting to lock monitor 0x15b5bd6c (object 0x046b3430, a java.lang.Object),
  which is held by "哲学家A"
"哲学家A":
  waiting to lock monitor 0x01c1705c (object 0x046b3438, a java.lang.Object),
  which is held by "哲学???B"

Java stack information for the threads listed above:
===================================================
//哲学家A占用了0x046b3430,等待0x046b3438,哲学家B正好相反,因此产生死锁
"哲学家B":
    at geym.conc.ch4.deadlock.DeadLock.run(DeadLock.java:42)
    - waiting to lock <0x046b3430> (a java.lang.Object)
    - locked <0x046b3438> (a java.lang.Object)
"哲学家A":
    at geym.conc.ch4.deadlock.DeadLock.run(DeadLock.java:29)
    - waiting to lock <0x046b3438> (a java.lang.Object)
    - locked <0x046b3430> (a java.lang.Object)

Found 1 deadlock.

上面显示了jstack的部分输出。可以看到,哲学家A和哲学家B两个线程发生了死锁。并且在最后,可以看到两者相互等待的锁的ID。同时,死锁的两个线程均处于BLOCK状态。

如果想避免死锁,除了使用无锁的函数外,另外一种有效的做法是使用第三章节介绍的重入锁,通过重入锁的中断或者限时等待可以有效规避死锁带来的问题。大家可以再回顾一下相关内容。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文