JavaRush /Java блог /Random /Разбор примера DeadLock.
fog
18 уровень

Разбор примера DeadLock.

Статья из группы Random
В посте "Помогите разобрать пример программы с многопоточностью и synchronized" камрад @Anonymous #2481584 попросил помочь разобраться что происходит в следующем коде:
package testpack;

class Deadlock implements Runnable {

    class A {
        synchronized void foo(B b) {
            String name = Thread.currentThread().getName();

            System.out.println(name + " entered A.foo");

            try {
                Thread.sleep(1000);
            } catch(Exception e) {
                System.out.println("A Interrupted");
            }

            System.out.println(name + " trying to call B.last()");
            b.last();
        }

        synchronized void last() {
            System.out.println("Inside A.last");
        }
    }

    class B {
        synchronized void bar(A a) {
            String name = Thread.currentThread().getName();
            System.out.println(name + " entered B.bar");

            try {
                Thread.sleep(1000);
            } catch(Exception e) {
                System.out.println("B Interrupted");
            }

            System.out.println(name + " trying to call A.last()");
            a.last();
        }

        synchronized void last() {
            System.out.println("Inside A.last");
        }
    }

        A a = new A();
        B b = new B();

        Deadlock() {
            Thread.currentThread().setName("MainThread");
            Thread t = new Thread(this, "RacingThread");
            t.start();

            a.foo(b); // get lock on a in this thread.
            System.out.println("Back in main thread");
        }

        public void run() {
            b.bar(a); // get lock on b in other thread.
            System.out.println("Back in other thread");
        }

        public static void main(String args[]) {
            new Deadlock();
        }
    }
Мой ответ получился довольно многословным, поэтому я решил оформить его в виде небольшой статьи. Возможно данная публикация окажется интересна кому-нибудь ещё, кто недавно впервые столкнулся с многопоточностью. Итак, давайте попытаемся понять, что же происходит в приведённом коде, когда возникает взаимная блокировка потоков. Так называемый "DeadLock":
  1. В первом потоке, создаётся новый объект класса DeadLock. В ходе работы конструктора, он создаёт второй поток, передав создаваемому потоку ссылку на конструируемый объект.
  2. Затем, в первом потоке, вызывается метод запуска второго потока. Второй поток начинает исполнять метод run(). Начинается гонка.
  3. В первом потоке управление передаётся методу foo() объекта a. Этот метод автоматически захватывает монитор объекта a, а затем в ходе выполнения 12 строки переводит первый поток в спящее состояние на одну секунду. Монитор объекта a всё ещё захвачен.
  4. В это время, второй поток передаёт управление методу bar() объекта b. Монитор объекта b автоматически захватывается. В ходе выполнения 32 строки, второй поток засыпает на одну секунду. Монитор объекта b остаётся захвачен.
  5. Далее либо первый поток проснётся раньше второго, либо второй проснётся раньше первого, либо они проснуться одновременно. Это не будет иметь никакого значения. Их судьба уже решена. Их ожидает взаимная блокировка.
  6. Просыпается первый поток, и в ходе выполнения 18 строки, передаёт управление методу last() объекта b. Этот метод пытается автоматически захватить монитор объекта b, но терпит неудачу, поскольку монитор объекта b всё ещё захвачен вторым потоком, который выполняет метод bar() объекта b. Так как необходимый монитор захватить не удалось, первый поток переводится в спящее состояние, ожидая освобождения монитора объекта b.
  7. Просыпается второй поток, и в ходе выполнения 38 строки, передаёт управление методу last() объекта a. Этот метод пытается автоматически захватить монитор объекта a, но терпит неудачу, поскольку монитор объекта a всё ещё захвачен первым потоком, который выполняет метод foo() объекта a. Так как необходимый монитор захватить не удалось, второй поток переводится в спящее состояние, ожидая освобождения монитора объекта a.
  8. В данной ситуации совершенно неважно, какой из потоков проснётся раньше, а какой позже, или они вообще проснуться одновременно. Результат всё равно один.
  9. Каждый поток заснул, ожидая освобождения так необходимого монитора, другим потоком. Но первый поток держит захваченным монитор объекта a, ожидая освобождения монитора объекта b, а второй поток, держит в захвате монитор объекта b, ожидая освобождения монитора объекта a.
  10. Гонка закончилась. Оба потока не могут продолжать выполнение. Взаимная блокировка. DeadLock.
Могут ли потоки, в ходе выполнения данного кода избежать взаимной блокировки? Да, могут. Хотя это маловероятно. Для этого необходимо выполнение одного из следующих условий:
  • Либо первый поток просыпается, и в ходе выполнения метода last(), объекта b, успевает захватить монитор этого объекта, прежде чем монитор упомянутого объекта будет захвачен вторым потоком во время исполнения метода bar().
  • Либо наоборот - второй поток просыпается, и в ходе выполнения метода last(), объекта a, успевает захватить монитор этого объекта, прежде чем монитор упомянутого объекта будет захвачен первым потоком во время исполнения метода foo().
Если будет исполнено какое-либо из условий, то один поток свободно завершит выполнение метода last(), после чего другой поток так же, без проблем, продолжит своё исполнение. Но, как я упомянул ранее, это маловероятно, так как потоки, в начале, засыпают на целую секунду (вечность, с точки зрения процессора), а JVM запускает выполнение потоков очень быстро.
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Anonymous #2481584 Уровень 30
15 апреля 2021
Огромное спасибо за такое подробное объяснение. Видимо, я ошибся, считая что метод типа

synchronized void foo(B b)
захватывает монитор обьекта b наподобие данного кода

synchronized (b)
Т.е., если можно уточнить, получается что

Class A {
   synchronized void foo(A b) {
   }
}
будет равносилен ?

Class A {
synchronized (this) {
   }
}
Если какой то поток заходит в синхронизированный метод обьекта (

synchronized void foo(A b) {
  } 
), то сам обьект автоматически становится "заблокированный" для других потоков? Если я правильно понял