Эквивалентный код для синхронизации метода экземпляра в Java - PullRequest
8 голосов
/ 06 января 2009

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

public synchronized void someMethod() {
  //stuff
}

и

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

Они эквивалентны?

Ответы [ 7 ]

13 голосов
/ 06 января 2009

Они эквивалентны по функциям, хотя протестированные мной компиляторы (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.

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

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

7 голосов
/ 06 января 2009

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

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

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

РЕДАКТИРОВАТЬ: Я собираюсь уточнить это, как некоторые люди здесь поняли это неправильно Рассмотрим:

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, даже если он не синхронизирован.

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

4 голосов
/ 06 января 2009

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

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

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

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

Да. При использовании ключевого слова synchronized в методе экземпляра используется this в качестве монитора, а при использовании метода класса (статический метод) используется класс Class object (Foo.class).

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

0 голосов
/ 21 апреля 2012

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

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

0 голосов
/ 16 марта 2009
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 могут быть вызваны одновременно
0 голосов
/ 06 января 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...