Java гарантированный тупик - PullRequest
0 голосов
/ 18 июня 2019

У меня есть два класса:

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 -.Я не понимаю почему?И я не совсем понимаю смысл концепции синхронизированного блока.Почему этот блок является частью кода потока, а не некоторой общей частью кода, которую выполняют разные потоки?

Ответы [ 2 ]

2 голосов
/ 18 июня 2019

Синхронизированный блок предотвращает одновременное выполнение кода на одном и том же объекте монитора. В этом случае doS1() объект монитора равен resource1, а в doS2 () объект монитора равен resource2. Когда поток входит в синхронизированный блок, он пытается получить блокировку объекта монитора. Если он получит блокировку, он продолжит работу и снимет блокировку только тогда, когда выйдет из блока (или снимет блокировку) Если он не может получить блокировку (поскольку другой поток уже имеет блокировку, то поток будет блокироваться до тех пор, пока блокировка не будет снята и он не сможет ее получить).

Два приведенных выше примера, Deadlock1 и Deadlock2, не выполняют эквивалентный код. В Deadllock1 оба объекта монитора не могут быть получены одним и тем же потоком в одно и то же время.

В Deadlock2 каждый поток пытается одновременно заблокировать оба объекта монитора.

  1. Поток 1 получает блокировку для ресурса1 и спит в течение 50 мс
  2. В то же время Поток 2 получает блокировку для ресурса2 и спит в течение 50 мс
  3. Когда поток 1 продолжается, он все еще имеет блокировку для resource1 и пытается получить блокировку для resource2. Поток 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();
    }
}
1 голос
/ 18 июня 2019

Deadlock1:

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'.

Поскольку оба потока никогда не имеют одинаковые ресурсы одновременно, это не проблема.

Deadlock2:

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 имеет resouce1, а поток 2 имеет ресурс 2, который также не может получить ресурс, следовательно, тупик.

И я не совсем понимаю смысл концепции синхронизированного блока.Почему этот блок является частью кода потока, а не некоторой общей частью кода, которую выполняют разные потоки?

Синхронизированный блок описывает часть кода, где удерживается блокировка ресурса (ссылка на объект),В начале блока получается блокировка (или код будет ждать, пока это не произойдет).Когда блок заканчивается, блокировка освобождается.

Метод с ключевым словом synchronized ведет себя так же, только когда блокировка получается для самого объекта.

Существуют различные способыПоддерживая эти блокировки, вы, конечно, можете использовать общедоступный код, который используют оба потока.

...