如何设计一组会出现死锁 (Deadlock) 的 ABAP 程序
我们在计算机操作系统这门专业课上,学过死锁(Deadlock)的概念:两个或两个以上的进程(或线程)在执行过程中,由于竞争资源而造成的一种阻塞的现象,称为死锁。若无外力干预,这些处于死锁状态的进程将永远处于互相等待的阻塞状态中。
正好我写这篇文章的时候,儿子走到我电脑前看到文章标题,好奇地问我什么是死锁。我解释道,“假设你和白妹妹(我儿子的玩伴)手上都有一张奥特曼白金卡,你特别想要白妹妹手上那张白金卡,白妹妹也特别想要你手上那张白金卡。你们都想让对方把他/她手上那张卡送给你们,但你们都舍不得把自己手上那张卡送出去。这就是死锁。”
儿子又问,那这种情况咋办。
我说,只有靠大人的介入。比如你老爸出马,把你们手中两张卡都没收了,等我玩够了再还给你们,这样你们就不会死锁了。
言归正传,在使用 ABAP 答这道面试题之前,我们先看看如何用 Java 编写一个会出现死锁的程序。
不到 40 行代码就完成任务。为了便于 ABAP 从业人员理解,没有使用 Java 里的 Lambda 表达式,否则代码可以更短。
该程序逻辑如下:
线程 1 持有资源 1,试图持有资源 2;线程 2 持有资源 2,试图持有资源 1;死锁发生;
现在进入 ABAP 部分。
ABAP 帮助文档提到,使用 SELECT SINGLE FOR UPDATE 读取单条记录时,会自动在数据库层面为该条记录设置一把锁(Exclusive lock,有的中文文档翻译成排他锁)。如果上锁操作会导致死锁发生,则会抛出异常。
于是我们首先创建一个 ABAP 数据库表,插入两条记录:
再开发两个 ABAP 程序 ZLOCK1 和 ZLOCK2,分别按照 Z01, Z02 和 Z02, Z01 的顺序使用 SELECT SINGLE FOR UPDATE 向数据库发起读取请求。
开启两个 SAPGUI 窗口,按照下面的步骤执行这两个程序,即可产生死锁。
(1) 以调试模式单步执行程序 ZLOCK1,成功执行完 SELECT SINGLE FOR UPDATE .. WHERE object_id = 'Z01', 意味着此时程序 ZLOCK1 在数据库层面对 Z01 这条记录设置了一把锁。
(2) 切换到另一个 SAPGUI 窗口,执行程序 ZLOCK2, 单步调试执行完语句 SELECT SINGLE FOR UPDATE .. WHERE object_id = 'Z02',即此时程序 ZLOCK2 在数据库层面对 Z02 成功上锁。
(3) 再回到窗口 1,继续调试程序 ZLOCK1,此时调试器会阻塞,因为 ZLOCK1 试图对 Z02 上锁,而此时程序 ZLOCK2 持有记录 Z02 的锁。程序 ZLOCK1 处于阻塞状态,等待 ZLOCK2 释放对记录 Z02 设置的锁。
(3) 回到窗口 2,继续在调试模式下执行 ZLOCK2 程序第 12 行语句。此时程序 ZLOCK2 试图对记录 Z01 上锁,但该记录已经被程序 ZLOCK1 锁住了,程序 ZLOCK2 只好等待程序 ZLOCK1 释放对记录 Z01 的锁;而程序 ZLOCK1 永远也不可能释放对记录 Z01 上的锁,因为此刻它已经处于阻塞状态了,在等待程序 ZLOCK2 释放对记录 Z02 的锁。
此时 ABAP Kernel 检测到程序 ZLOCK2 发生了死锁,抛出一个运行时异常,结束了 ZLOCK2 的执行。程序 ZLOCK2 持有对记录 Z02 的锁也自动释放了。最后的结果是,程序 ZLOCK1 成功地对记录 Z02 上了锁。
在事务码 ST22 里,我们能观察到这次死锁的详情。
ABAP Kernel 这种检测到死锁发生后,立刻终止程序运行的方式,节省了应用开发人员通过阅读代码去分析死锁的时间。
回到 Jerry 之前的 Java 程序,运行之后两个线程陷入死锁,在控制台上没有打印任何可供排错的信息。然而 JDK 本身提供了方便的检查 Java 应用死锁状态的命令行工具:jstack.
JDK(Java Development Kit)是 Java 开发的核心工具集,其中包含了一系列的开发、调试和性能分析工具。其中,jstack 是 JDK 自带的一个命令行工具,用于生成 Java 虚拟机(JVM)当前运行状态下的线程快照。
线程快照是指在某个特定时间点上,记录下 JVM 中所有线程的信息,包括线程的状态、调用堆栈(调用栈)以及相关的锁信息。通过分析线程快照,可以了解到程序中的线程是如何运行的,以及是否存在死锁、死循环等问题。
使用 jstack 命令可以在命令行窗口中执行,它会连接到指定的 Java 进程(JVM),并输出当前线程的信息到控制台或指定的输出文件中。jstack 提供了一些有用的选项,可以用于生成线程快照并进行进一步的分析,例如查看每个线程的堆栈跟踪、查找死锁等。
下面我们来动手实际操作一下。命令行执行 jstack,传入 Java 程序的进程 id,如果该程序发生了死锁,该工具会打印出程序里具体是哪一行代码,在试图对何种资源进行上锁操作时出现的死锁,从而帮助开发人员迅速定位到逻辑上存在缺陷的代码位置。
总结
本文首先从计算机专业课上学习的死锁概念出发,接着先后给出了用 Java 和 ABAP 两种编程语言编写一个简单的会触发死锁的简单程序,通过实战帮助大家加深对死锁这个概念的理解。最后给出了如何使用 JDK 自带的 jstack 这个工具来检测自己 Java 程序中潜在的死锁隐患。
希望本文这两个小小的例子能帮助大家加深对死锁这个基础知识点的掌握,感谢阅读。
版权声明: 本文为 InfoQ 作者【Jerry Wang】的原创文章。
原文链接:【http://xie.infoq.cn/article/706a494ea74e77b500480ff69】。文章转载请联系作者。
评论