Блокировка реентера

Немного о помощи, пожалуйста, рассмотрите бит кода ниже.

public class Widget {
    public synchronized void doSomething() {
        ...
    }
}

public class LoggingWidget extends Widget {
    public synchronized void doSomething() {
        System.out.println(toString() + ": calling doSomething");
        super.doSomething();
    }
}

Я читал, что когда doSomething() в LoggingWidget вызывается, JVM попытается сначала получить блокировку в LoggingWidget, а затем в Widget.

Мне любопытно узнать причину. Это связано с тем, что JVM знает, что doSomething() имеет вызов super.doSomething() или потому, что вызов метода подкласса всегда будет получать блокировку над суперклассом.

Приветствия

Ответ 1

Вы ошибаетесь - блокировки получаются на уровне экземпляра. В вашем примере есть только один замок, потому что только один экземпляр создается, когда вы говорите:

Widget w = new LoggingWidget

Вы можете просматривать блокировки (также известные как мониторы, мьютексы или семафоры) как индивидуально "прикрепленные" к каждому экземпляру объекта в JVM.

Если у вас был еще один метод synchronized в подклассе LoggingWidget, вы увидите, что это правда. Невозможно было бы вызвать этот (новый) метод и метод doSomething в то же время [с разными потоками на одном объекте].

Это также справедливо для другого метода synchronized на суперклассе (то есть, это никак не влияет на переопределенные методы).

Ответ 2

public synchronized void doSomething() {
    System.out.println(toString() + ": calling doSomething");
    super.doSomething();
}

совпадает с:

public void doSomething() {
    synchronized (this) {
        System.out.println(toString() + ": calling doSomething");
        super.doSomething();
    }
}

Вы блокируете экземпляр, а не класс. Поэтому, когда вызывается super.doSomething(), вы уже заблокировали этот экземпляр.

Ответ 3

Есть только один экземпляр для блокировки, экземпляр LoggingWidget, никогда не существует фактического экземпляра Widget.

Блокировка получается, когда вы вызываете LoggingWidget.doSomething(), и поскольку у вас уже есть блокировка при вызове super.doSomething(), этот метод выполняется как обычно.

Ответ 4

Reentrancy работает, сначала приобретая замок. Когда один поток приобретает замок, он известен в jvm. При вводе блока кода, синхронизированного с потоком, который в настоящее время удерживает блокировку, им разрешено продолжить работу без повторного приобретения блокировки. Затем jvm увеличивает счетчик при каждом повторном действии, дополнительно уменьшаясь при выходе из этого блока до тех пор, пока счет не будет равен нулю. Когда счетчик равен нулю, блокировка освобождается.

Ответ 5

B.Goetz - "JJava Concurrency in Practice", если встроенные блокировки не были реентерабельными, вызов super.doSomething никогда не сможет получить блокировку, потому что он будет считаться уже сохраненным, и поток будет постоянно останавливаться ожидая блокировки, которую он никогда не сможет приобрести. Reentrancy спасает нас от тупика в таких ситуациях.