Рубрики
Без рубрики

Тупик на Яве: Повесть о двух поварах

Взаимоблокировка – это сценарий, в котором, как минимум, два потока блокируют друг друга, удерживая ресурсы, которые каждый… Помеченный java.

Взаимоблокировка – это сценарий, в котором, как минимум, два потока блокируют друг друга, удерживая ресурсы, необходимые друг другу для завершения. Ресурсы отслеживаются по состоянию, и эти ресурсы совместно используются потоками. При таких условиях возникает вероятность тупиковой ситуации. Ключевыми фразами, которые следует отметить здесь, являются “общие ресурсы с отслеживанием состояния” и “несколько потоков”.

Сейчас я чувствую себя довольно голодным. Оставляя в стороне определение тупика, давайте немного подкрепимся. Шеф-повар Том и шеф-повар Джерри уже некоторое время были на кухне, чтобы приготовить для меня два разных блюда. Я вернусь через минуту после того, как проконсультируюсь со своими поварами. (… через некоторое время) Похоже, что эффект размышлений о “Тупике” оказывает свое влияние на кухню. Ни шеф-повар Том, ни шеф-повар Джерри не закончили свои блюда. Они ждали, когда будут использованы общие для них ингредиенты. Другими словами, набор ингредиентов должен был быть общим для поваров на кухне. Кроме того, у этих ингредиентов есть свое собственное состояние.

Ниже представлено закодированное представление тупика, возникшего на кухне. Здесь задействованы такие организации, как Ингредиент, шеф-повар Том, шеф-повар Джерри. Для упрощения набор ингредиентов ограничен всего двумя – “соль” и “перец”. Эти ингредиенты должны быть разделены между поварами.

Реализация класса для “Ингредиента”.

class Ingredient {
  private String name;
  Ingredient(String ingredientName) { name = ingredientName;}
  @Override public String toString() {
    return this.name;
  }
}

Реализация класса для Шеф-повара Тома . Обратите внимание на порядок применения ингредиента. Том применяет соль , перец и соль снова.

class CookTom implements Runnable {

  ...
  @Override
  public void run() {
    Ingredient salt = store.get("salt");
    Ingredient pepper = store.get("pepper");
    synchronized(salt) {
      ...
      synchronized(pepper){...}
      ...
    }
  }
}

Реализация класса для Шеф-повара Джерри . Обратите внимание на порядок применения ингредиента. Том применяет перец , соль и перец снова.

class CookJerry implements Runnable {

  @Override
  public void run() {
    ...
    synchronized(pepper) {
      ...
      synchronized(salt){...}
      ...
    }
  }
}

Хотя это кажется совершенно правильным порядком добавления ингредиентов, существует вероятность того, что вы попадете в тупик. Как? Ресурсы являются общими. Итак, Том придерживается Соли, но нуждается в Перце. Том не отдает и не отдает соль сразу после добавления соли в первый раз. Точно так же Джерри не отдает перец сразу после его использования. Это приводит к тупику.

Вот полный код.

import static java.lang.System.out;

import java.util.HashMap;
import java.util.Map;

class DeadLock {
  public static void main(String [] args) {
    log("Demo: Deadlock");
    Ingredient salt = new Ingredient("salt");
    Ingredient pepper = new Ingredient("pepper");
    Map store = new HashMap();
    store.put(salt.toString(), salt);
    store.put(pepper.toString(), pepper);
    CookTom tom = new CookTom(store);
    CookJerry jerry = new CookJerry(store);
    Thread dish1 = new Thread(tom);
    Thread dish2 = new Thread(jerry);
    dish1.start();
    dish2.start();
  }

  static void log(String message) {
    out.printf("%s \n", new Object[]{message});
  }
  static void waitForAWhile(){
    try {
      Thread.sleep(1000 * 3);
    } catch (InterruptedException e) {}
  }
}
class Ingredient {
  private String name;
  Ingredient(String ingredientName) { name = ingredientName;}
  @Override public String toString() {
    return this.name;
  }
}

class CookTom implements Runnable {

  private final Map store;
  CookTom(Map ingredientStore) { store = ingredientStore;}

  @Override
  public void run() {
    Ingredient salt = store.get("salt");
    Ingredient pepper = store.get("pepper");
    synchronized(salt) {
      DeadLock.log(this.toString() + " [has] adds " + salt);
      DeadLock.waitForAWhile();
      DeadLock.log(this.toString() + " [needs] " + pepper);
      synchronized(pepper){
        DeadLock.log(this.toString() + "[has] adds" + pepper);
      }
      DeadLock.log(this.toString() + " [has] adds " + salt);
    }
  }

  @Override
  public String toString() {
    return "Cook Tom  ";
  }
}

class CookJerry implements Runnable {

  private final Map store;
  CookJerry(Map ingredientStore) { store = ingredientStore;}

  @Override
  public void run() {
    Ingredient salt = store.get("salt");
    Ingredient pepper = store.get("pepper");
    synchronized(pepper) {
      DeadLock.log(this.toString() + " [has] adds " + pepper);
      DeadLock.log(this.toString() + " [needs] " + salt);
      synchronized(salt){
        DeadLock.log(this.toString() + "[has] adds" + salt);
      }
      DeadLock.log(this.toString() + " [has] adds " + pepper);      
    }
  }
  @Override
  public String toString() {
    return "Cook Jerry";
  }
}

Дампы потоков JVM 8 также обнаруживают взаимоблокировку. На компьютере с Linux найдите идентификатор процесса, выполняющего приведенный выше java-код, и используйте jstack для сброса потока. Фрагмент сообщения о блокировке дампа потока приведен ниже.


> jstack $pid
...
Java stack information for the threads listed above:
===================================================
"Thread-1":
  at dev.mercury.CookJerry.run(Deadlock.java:78)
  - waiting to lock <0x00000007199b0d00> (a dev.mercury.Ingredient)
  - locked <0x00000007199b0d40> (a dev.mercury.Ingredient)
  at java.lang.Thread.run(Thread.java:748)
"Thread-0":
  at dev.mercury.CookTom.run(Deadlock.java:54)
  - waiting to lock <0x00000007199b0d40> (a dev.mercury.Ingredient)
  - locked <0x00000007199b0d00> (a dev.mercury.Ingredient)
  at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

`

Оригинал: “https://dev.to/dexter2305/deadlock-a-tale-of-two-cooks-478i”