Взаимоблокировка – это сценарий, в котором, как минимум, два потока блокируют друг друга, удерживая ресурсы, необходимые друг другу для завершения. Ресурсы отслеживаются по состоянию, и эти ресурсы совместно используются потоками. При таких условиях возникает вероятность тупиковой ситуации. Ключевыми фразами, которые следует отметить здесь, являются “общие ресурсы с отслеживанием состояния” и “несколько потоков”.
Сейчас я чувствую себя довольно голодным. Оставляя в стороне определение тупика, давайте немного подкрепимся. Шеф-повар Том и шеф-повар Джерри уже некоторое время были на кухне, чтобы приготовить для меня два разных блюда. Я вернусь через минуту после того, как проконсультируюсь со своими поварами. (… через некоторое время) Похоже, что эффект размышлений о “Тупике” оказывает свое влияние на кухню. Ни шеф-повар Том, ни шеф-повар Джерри не закончили свои блюда. Они ждали, когда будут использованы общие для них ингредиенты. Другими словами, набор ингредиентов должен был быть общим для поваров на кухне. Кроме того, у этих ингредиентов есть свое собственное состояние.
Ниже представлено закодированное представление тупика, возникшего на кухне. Здесь задействованы такие организации, как Ингредиент, шеф-повар Том, шеф-повар Джерри. Для упрощения набор ингредиентов ограничен всего двумя – “соль” и “перец”. Эти ингредиенты должны быть разделены между поварами.
Реализация класса для “Ингредиента”.
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"); Mapstore = 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”