Эквивалентный код, например, синхронизация метода в Java

Обсуждая синхронизацию Java question, кто-то сделал комментарий, что следующие фрагменты не эквивалентны (и могут скомпилировать разные байт-коды):

public synchronized void someMethod() {
  //stuff
}

и

public void someMethod() {
  synchronized (this) {
    //stuff
  }
}

Являются ли они эквивалентными?

Ответы

Ответ 1

Они эквивалентны в функции, хотя тестируемые компиляторы (Java 1.6.0_07 и Eclipse 3.4) генерируют разные байт-коды. Первый генерирует:

// access flags 33
public synchronized someMethod()V
  RETURN

Вторая генерирует:

// access flags 1
public someMethod()V
  ALOAD 0
  DUP
  MONITORENTER
  MONITOREXIT
  RETURN

(Благодаря ASM для печати байт-кода).

Таким образом, разница между ними сохраняется на уровне байт-кода, и это до JVM, чтобы сделать их поведение одинаковым. Тем не менее, они имеют одинаковый функциональный эффект - см. пример в Спецификации языка Java.

Следует отметить, что если метод переопределен в подклассе, он не обязательно синхронизируется - поэтому в этом отношении нет никакой разницы.

Я также проверил тест, чтобы заблокировать поток, пытающийся получить доступ к монитору в каждом случае, чтобы сравнить то, что их трассировки стека будут выглядеть в дампе потока, и оба они содержали данный метод, поэтому нет никакой разницы.

Ответ 2

Я сделал оригинальный комментарий, что утверждения идентичны.

В обоих случаях первое, что происходит, - это то, что вызывающий поток попытается получить текущий монитор (т.е. this ').

Я не знаю о разных байткодах, я буду рад услышать разницу. Но на практике они на 100% идентичны.

EDIT: Я собираюсь разъяснить это, поскольку некоторые люди здесь ошибаются. Рассмотрим:

public class A {
    public synchronized void doStuff()
    {
        // do stuff
    }
}

public class B extends A {
    public void doStuff()
    {
        // do stuff
        // THIS IS OVERRIDE!
    }
}

В этом случае doStuff() в классе B по-прежнему переопределяет doStuff() в классе A, даже если он не синхронизирован.

Синхронизированное ключевое слово никогда не является частью контракта! Не для подклассов, а не для интерфейсов, а не для абстрактных классов.

Ответ 3

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

Я не добавил ничего другого, чтобы оправдать его в то время, потому что нечего оправдывать - они просто компилируются в разные байт-коды. Если вы объявляете метод как синхронизированный, то эта синхронизация является частью определения метода. Синхронизированный блок внутри метода не является частью определения метода, но вместо этого он включает отдельные байт-коды для получения и выпуска монитора, как иллюстрировал один из вышеперечисленных плакатов. Строго говоря, это немного разные вещи, хотя для общей логики вашей программы, они эквивалентны.

Когда это имеет значение? Ну, на большинстве современных настольных ВМ, почти никогда. Но, например:

  • VM могла в принципе сделать оптимизацию в одном случае, но не в другом
  • есть некоторые оптимизаторы компилятора JIT, где количество байт-кодов в методе принимается как критерий для того, какие оптимизации сделать
  • VM без JIT-компилятора (правда, немногие в настоящее время, но, возможно, на более старом мобильном устройстве?) будет иметь больше байт-кодов для обработки при каждом вызове

Ответ 4

Да. Использование синхронизированного ключевого слова в методе экземпляра использует 'this' в качестве монитора, а также его использование в методе класса (статический метод) использует класс "Объект класса (Foo.class).

Таким образом, вы можете синхронизировать целые методы и в то же время синхронизировать их с фрагментом кода в другом методе с использованием стиля синхронизированного блока.

Ответ 5

Я не вижу никакой функциональной разницы - как синхронизировать их все тела метода на (это). Как человек, который прокомментировал, что они разные, оправдывает их утверждение?

Ответ 6

Они не совсем эквивалентны в функции. Другой код может использовать отражение, чтобы увидеть, имеет ли ваш метод синхронизированный модификатор, но нет способа определить, содержит ли метод синхронизированный блок без чтения его байт-кода.

Иногда бывает полезно определить время синхронизации. Лично я использовал этот флаг, чтобы избежать избыточной блокировки при выполнении синхронизации в аспектно-ориентированном программировании.

Ответ 7

MyObject myObjectA;
MyObject myObjectB;

public void someMethod() {
  synchronized (this) {
    //stuff
  }
}

public void someMethodA() {
  synchronized (myObjectA) {
    //stuff
  }
}

public void someMethodB() {
  synchronized (myObjectB) {
    //stuff
  }
}

В этом случае:

  • someMethod блокирует весь класс
  • someMethodA только myObjectA
  • someMethodB только myObjectB
  • someMethodA и someMethodB могут быть вызваны одновременно