原文: https://javatutorial.net/livelock-and-deadlock-in-java

以下文章讨论了 Java 中的活锁和死锁状态,它们如何发生以及如何避免它们。

活锁

Java 中的活锁是一种递归条件,其中两个或多个线程不断重复一段特定的代码。

当一个线程不断响应另一个线程并且另一个线程也执行相同操作时,就会发生活锁。

要对其进行分解,我们可以总结以下几点:

  • 一个线程响应另一个线程的行为而运行,而另一个线程也响应先前的线程而运行,则可能发生活锁。
  • 活锁线程无法继续进行。
  • 线程没有被阻塞; 他们只是忙于互相回应。

活锁也被称为资源匮乏的特例

让我们通过将其与现实世界联系起来来理解该概念。 考虑两辆车在狭窄桥梁的相对两侧。 一次只能乘一辆车通过桥。 这两辆车的驾驶员都很有礼貌,正在等待对方先通过桥。 他们互相鸣叫,让他们知道他们想让对方先通过。 但是,两者都没有越过桥梁并互相鸣喇叭。 这种情况类似于活锁。

现在,通过一些编码来尝试这种实际情况:

第一辆等待过桥的汽车的等级:

  1. public class Car1 {
  2. private boolean honking = true;
  3. public void passBridge(Car2 car2) {
  4. while (car2.hasPassedBridge()) {
  5. System.out.println("Car1 waiting to pass the bridge");
  6. try {
  7. Thread.sleep(1000);
  8. } catch (InterruptedException ex) {
  9. ex.printStackTrace();
  10. }
  11. }
  12. System.out.println("Passed bridge");
  13. this.honking= false;
  14. }
  15. public boolean hasPassedBridge() {
  16. return this.honking;
  17. }
  18. }

等待通过桥的第二辆车的类别:

  1. public class Car2 {
  2. private boolean honking = true;
  3. public void passBridge(Car1 car1) {
  4. while (car1.hasPassedBridge()) {
  5. System.out.println("Car 2 is waiting to pass the bridge!");
  6. try {
  7. Thread.sleep(1000);
  8. } catch (InterruptedException ex) {
  9. ex.printStackTrace();
  10. }
  11. }
  12. System.out.println("Car 2 has passed the bridge!");
  13. this.honking = false;
  14. }
  15. public boolean hasPassedBridge() {
  16. return this.honking;
  17. }
  18. }

主要测试类别:

  1. public class BridgeCheck {
  2. static final Car2 car2 = new Car2();
  3. static final Car1 car1 = new Car1();
  4. public static void main(String[] args) {
  5. Thread t1 = new Thread(new Runnable() {
  6. public void run() {
  7. car2.passBridge(car1);
  8. }
  9. });
  10. t1.start();
  11. Thread t2 = new Thread(new Runnable() {
  12. public void run() {
  13. car1.passBridge(car2);
  14. }
  15. });
  16. t2.start();
  17. }
  18. }

输出:

这导致了非终止循环。

死锁

死锁与活锁有些不同。 死锁是一种状态,其中每个成员都在等待其他成员释放锁。

可能存在以下情况:一个线程正在等待另一个线程获取的对象锁,而第二个线程正在等待第一个线程获取的对象锁。 由于两个线程都在互相等待释放锁,因此这种情况称为死锁。

Java 中的活锁和死锁 - 图1

图:死锁状态

让我们看一下发生死锁的情况:

  1. public class DeadlockExample{
  2. private static String A = "Something A";
  3. private static String B = "Something B";
  4. public void someFunction(){
  5. synchronized(A){//may deadlock here
  6. synchronized(B){
  7. // function does some work here
  8. }
  9. }
  10. }
  11. public void someOtherFunction(){
  12. synchronized(B){//may deadlock here
  13. synchronized(A){
  14. // the function does something here
  15. }
  16. }
  17. }
  18. }

考虑两个线程 T1 和 T2,T1 获取 A 并等待 B 完成其功能。 但是,T2 获取 B 并等待 A 完成其自身的功能。 在这里,T1 和 T2 正在等待被其他线程锁定的资源。 因此,这是一个僵局方案。

避免死锁:

  • 第一个建议是避免同时使用多线程,但这在许多情况下可能不切实际。 因此,这种解决方案不是很明智。
  • 分析并确保在事先访问资源时没有锁。
  • 在上面的编码示例中,为避免死锁,只需管理访问资源的顺序(访问 A 和 B 的顺序)。
  • 避免一次持有多个锁,以防万一您必须始终以相同的顺序获得这些锁。
  • 避免在持有锁的同时执行外来代码。
  • 尝试使用可中断的锁,以便即使遇到死锁,也可以中断这些锁,并且可以毫无问题地执行该过程。