В посте "Помогите разобрать пример программы с многопоточностью и 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":- В первом потоке, создаётся новый объект класса
DeadLock
. В ходе работы конструктора, он создаёт второй поток, передав создаваемому потоку ссылку на конструируемый объект. - Затем, в первом потоке, вызывается метод запуска второго потока. Второй поток начинает исполнять метод
run()
. Начинается гонка. - В первом потоке управление передаётся методу
foo()
объектаa
. Этот метод автоматически захватывает монитор объектаa
, а затем в ходе выполнения 12 строки переводит первый поток в спящее состояние на одну секунду. Монитор объектаa
всё ещё захвачен. - В это время, второй поток передаёт управление методу
bar()
объектаb
. Монитор объектаb
автоматически захватывается. В ходе выполнения 32 строки, второй поток засыпает на одну секунду. Монитор объектаb
остаётся захвачен. - Далее либо первый поток проснётся раньше второго, либо второй проснётся раньше первого, либо они проснуться одновременно. Это не будет иметь никакого значения. Их судьба уже решена. Их ожидает взаимная блокировка.
- Просыпается первый поток, и в ходе выполнения 18 строки, передаёт управление методу
last()
объектаb
. Этот метод пытается автоматически захватить монитор объектаb
, но терпит неудачу, поскольку монитор объектаb
всё ещё захвачен вторым потоком, который выполняет методbar()
объектаb
. Так как необходимый монитор захватить не удалось, первый поток переводится в спящее состояние, ожидая освобождения монитора объектаb
. - Просыпается второй поток, и в ходе выполнения 38 строки, передаёт управление методу
last()
объектаa
. Этот метод пытается автоматически захватить монитор объектаa
, но терпит неудачу, поскольку монитор объектаa
всё ещё захвачен первым потоком, который выполняет методfoo()
объектаa
. Так как необходимый монитор захватить не удалось, второй поток переводится в спящее состояние, ожидая освобождения монитора объектаa
. - В данной ситуации совершенно неважно, какой из потоков проснётся раньше, а какой позже, или они вообще проснуться одновременно. Результат всё равно один.
- Каждый поток заснул, ожидая освобождения так необходимого монитора, другим потоком. Но первый поток держит захваченным монитор объекта
a
, ожидая освобождения монитора объектаb
, а второй поток, держит в захвате монитор объектаb
, ожидая освобождения монитора объектаa
. - Гонка закончилась. Оба потока не могут продолжать выполнение. Взаимная блокировка. DeadLock.
- Либо первый поток просыпается, и в ходе выполнения метода
last()
, объектаb
, успевает захватить монитор этого объекта, прежде чем монитор упомянутого объекта будет захвачен вторым потоком во время исполнения методаbar()
. - Либо наоборот - второй поток просыпается, и в ходе выполнения метода
last()
, объектаa
, успевает захватить монитор этого объекта, прежде чем монитор упомянутого объекта будет захвачен первым потоком во время исполнения методаfoo()
.
last()
, после чего другой поток так же, без проблем, продолжит своё исполнение. Но, как я упомянул ранее, это маловероятно, так как потоки, в начале, засыпают на целую секунду (вечность, с точки зрения процессора), а JVM запускает выполнение потоков очень быстро.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
synchronized
-методы объекта, автоматически захватывают монитор объекта. Фактически код: Это более удобная форма записи следующего кода:static synchoronized
методы захватывают монитор самого класса, в котором они объявлены. Т.е. код: будет эквивалентен коду: