在这里,我们研究死锁的起源,考虑在现有代码中发现死锁的方法,并学习如何设计无死锁的同步。
线程同步是克服多线程程序中竞争条件的好工具。但是,它也有阴暗面。死锁:难以发现,重现和修复的令人讨厌的错误。防止它们发生的唯一可靠方法是正确设计您的代码,这是本文的主题。我们将研究死锁的起源,考虑在现有代码中发现潜在死锁的方法,并介绍设计无死锁同步的实用方法。这些概念将通过一个简单的演示项目进行说明。
假定读者已经熟悉多线程编程,并且对Java中的线程同步原语有很好的理解。在以下各节中,我们将不会在同步语句和锁API之间进行区分,而是将术语“锁”用于两种类型的可重入锁,在示例中更喜欢前者。
1.死锁机制
让我们回顾一下僵局的工作方式。请考虑以下两种方法:
这些方法是有意设计的,以产生死锁。让我们详细考虑这是如何发生的。
两个增量()和减量()基本上由以下5个步骤:
显然,只有相应的锁是空闲的,两种方法中的步骤1和2才可以通过,否则,执行线程将不得不等待它们的释放。
假设有两个并行线程,一个执行增量(),另一个执行减量()。每个线程的步骤将以正常顺序执行,但是,如果我们将两个线程放在一起考虑,则一个线程的步骤将与另一个线程的步骤随机交织。随机性来自系统线程调度程序施加的不可预测的延迟。可能的交织模式或场景非常多(准确地说,有252种),并且可以分为两组。第一组是其中一个线程足够快地获取两个锁的位置
该组中的所有情况均导致成功完成。
在第二组中,两个线程均成功获取锁。
该组中的所有情况都会导致第一个线程等待第二个线程拥有的锁,而第二个线程等待第一个线程拥有的锁,因此两个线程无法继续进行下去:
这是典型的死锁情况,具有所有典型特性。让我们概述重要的:
- 至少有两个线程,每个线程至少具有两个锁。
- 死锁仅在线程定时的特定组合时发生。
- 死锁的发生取决于锁定顺序。
第二个属性意味着无法随意复制死锁。此外,它们的可重复性取决于操作系统,CPU频率,CPU负载和其他因素。后者意味着软件测试的概念仅适用于死锁,因为同一代码在一个系统上可能完