DeadLock та його причини - 1

— Привіт, Аміго!

Сьогодні я тобі розповім, що таке дедлок (Dead Lock) — взаємне блокування.

— Так, я вже дещо знаю про це.

— І справді. Але сьогодні ми розглянемо цю тему детальніше.

У найпростішому випадку в дедлоку беруть участь два потоки й два об'єкта-м'ютекса. Взаємне блокування виникає, коли:

А) Кожному потоку під час роботи потрібно захопити обидва м'ютекса.

Б) Перший потік захопив перший м'ютекс і чекає, поки звільниться другий.

В) Другий потік захопив другий м'ютекс і чекає, поки звільниться перший.

Приклади:

Приклад
 public class Student
{
 private ArrayList friends = new ArrayList();

 public synchronized ArrayList getFriends()
 {
  synchronized(friends)
  {
   return new ArrayList(friends);
  }
 }

 public synchronized int getFriendsCount()
 {
  return friends.size();
 }

 public int addFriend(Student student)
 {
  synchronized(friends)
  {
   friends.add(student)
   return getFriendsCount();
  }
 }
}

Припустимо, перший потік викликав метод getFriends. У такому разі він спочатку захопить м'ютекс об'єкта this, а потім м'ютекс об'єкта friends.

Водночас другий потік викликав метод addFriend. Спочатку він захоплює м'ютекс об'єкта friends, а потім м'ютекс об'єкта this (при виклику getFriendsCount).

Спочатку все буде добре, але, як каже Закон Мерфі, якщо прикрість може статися, вона станеться. Обов'язково виникне ситуація, коли перший потік встигне захопити лише один м'ютекс, а другий потік у цей час захопить другий. Тож вони вічно будуть висіти в очікуванні, що хтось із них першим звільнить м'ютекс.

А ось ще один простий приклад:

Приклад
class KnightUtil
{
 public static void kill(Knight knight1, Knight knight2)
 {
  synchronized(knight1)
  {
   synchronized(knight2)
   {
    knight2.live = 0;
    knight1.experience +=100;
   } 
  }
 }
}

Є гра, де два лицарі борються один з одним. Перший лицар вбиває другого. Цю поведінку відображено в методі kill. Туди передаються два об'єкти-лицаря.

Спочатку ми захищаємо обидва об'єкти, щоб ніхто більше не міг їх змінити.

Другий лицар помирає (live=0).

Перший лицар отримує +100 досвіду.

І ніби все чудово, але часом може виникнути ситуація, коли другий лицар у цей час атакує першого. Для нього теж викличеться цей метод, але лицарі передаються в іншому порядку.

— Тобто нам навіть не потрібно кілька методів, щоб отримати дедлок?

— Саме так. Іноді достатньо одного простого методу, в якому вже відбуваються процеси, що здатні привести до зависання потоків та всієї програми.

— Так, тепер очевидно, що це явище зустрічається частіше, ніж я думав. Дякую за пояснення!