Java 保证死锁

Java guaranteed deadlock

我有两个类:

Deadlock1.java

class Client {
   final Object resource1 = "resource1";
   final Object resource2 = "resource2";
   void doS1() {
       synchronized(resource1) {} 
    }
    void doS2() {
       synchronized(resource2) {}
    }
 }

public class Deadlock1 {
  public static void main(String[] args) {
  Client client = new Client();
  new Thread(
      () ->
             {
               client.doS1();
               try {
                Thread.sleep(50);
              } catch (InterruptedException e) {
             }
              client.doS2();
      }).start();

     new Thread(
      () ->
             {
              client.doS2();
              try {
                Thread.sleep(50);
              } catch (InterruptedException e) {
            }
             client.doS1();
      }).start();
  }
}

Deadlock2.java

class Client {
    final Object resource1 = "resource1";
    final Object resource2 = "resource2";  
}

public class Deadlock2{
  public static void main(String[] args) {
     Client client = new Client();

     new Thread(
      () ->
      {
        synchronized (client.resource1) {
        try {
            Thread.sleep(50);
          } catch (InterruptedException e) {
       }

       synchronized (client.resource2) {}
        }
      }).start();

     new Thread(
      () ->
             {
        synchronized (client.resource2) {   
          try {
            Thread.sleep(50);
          } catch (InterruptedException e) {
          }
              synchronized (client.resource1) {}
        }
      }).start();
  }
}

在 Deadlock1 中没有发生死锁,但在 Deadlock2 中发生了。我不明白为什么?而且我不太明白synchronized block这个概念的含义。为什么这个块是线程代码的一部分而不是不同线程执行的一些公共代码?

死锁 1:

Thread 1 is acquiring the lock on 'resource1' and immediately releases it.
Thread 1 is waiting for x milliseconds.
Thread 1 is trying to acquire the lock on 'resource2'.

Thread 2 is acquiring the lock on 'resource2' and immediately releases it.
Thread 2 is waiting for x milliseconds.
Thread 2 is trying to acquire the lock on 'resource1'.

因为两个线程永远不会同时拥有相同的资源,所以这不是问题。

死锁 2:

Thread 1 is acquiring the lock on 'resource1' and holds it.
Thread 1 is waiting for x milliseconds.
Thread 1 is trying to acquire the lock on 'resource2'.

Thread 2 is acquiring the lock on 'resource2' and and holds it.
Thread 2 is waiting for x milliseconds.
Thread 2 is trying to acquire the lock on 'resource1'.

由于线程1有资源1,线程2有资源2,都得不到资源,所以死锁。

And i do not quite understand the meaning of the concept of synchronized block. Why this block is part of thread code but not some common piece of code which different threads execute?

同步块描述了代码的一部分,其中资源(对象引用)上的锁被持有。在块的开头获得锁(或者代码将等待直到发生这种情况)。当块结束时,锁被释放。

具有关键字 synchronized 的方法的行为相同,只是在对象本身上获得了锁。

有多种方法可以维护这些锁,您当然可以使用两个线程都在使用的公共共享代码。

同步块可防止在同一监视器对象上同时执行代码。在 doS1() 的情况下,监视器对象是 resource1,而在 doS2() 中,监视器对象是 resource2。当线程进入同步块时,它会尝试获取监视器对象上的锁。如果它获得锁,它将继续并仅在退出块时释放锁(或释放锁)。如果它无法获得锁(因为另一个线程已经拥有锁,那么该线程将阻塞,直到锁被释放并且它可以获取它)。

上面的两个示例 Deadlock1Deadlock2 没有执行等效代码。在 Deadllock1 中,两个监视器对象锁不能由同一个线程同时获取。

Deadlock2 中,每个线程都试图同时获取两个监视器对象的锁。

  1. 线程 1 获取资源 1 的锁并休眠 50 毫秒
  2. 同时线程 2 获取资源 2 的锁并休眠 50 毫秒
  3. 当线程 1 继续运行时,它仍然持有资源 1 上的锁,并试图获取资源 2 上的锁。 resource2 锁仍由线程 2 持有,因此它阻塞等待 resource2 被释放。
  4. 同时,线程 2 尝试获取资源 1 上的锁,但这仍然由线程 1 持有,因此它阻塞等待线程 1 释放资源 1 的监视器。
  5. 现在两个线程都被阻塞,等待监视器对象被释放,并且由于它们都被阻塞,它们无法释放它们已锁定的监视器对象...因此出现死锁。

如果我们重写 Deadlock1 以模仿 Deadlock2 的功能,那么它确实会产生死锁,它看起来像这样:

public class Deadlock1 {
    static class Client {
        final Object resource1 = "resource1";
        final Object resource2 = "resource2";

        void doS1() {
            synchronized (resource1) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                }
                doS2();
            }
        }

        void doS2() {
            synchronized (resource2) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                }
                doS1();
            }
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        new Thread(client::doS1).start();
        new Thread(client::doS2).start();
    }
}

或者,如果我们重写 Deadlock2 以模仿 Deadlock1 的功能,那么它不会产生死锁,它看起来像这样:

public class Deadlock2 {
    static class Client {
        final Object resource1 = "resource1";
        final Object resource2 = "resource2";
    }

    public static void main(String[] args) {
        Client client = new Client();

        new Thread(
                () ->
                {
                    synchronized (client.resource1) {
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                        }

                    }
                    synchronized (client.resource2) {}
                }).start();

        new Thread(
                () ->
                {
                    synchronized (client.resource2) {
                        try {
                            System.out.println("3");
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                        }
                    }
                    synchronized (client.resource1) {}
                }).start();
    }
}