• ,

Миф о (не)равнозначности быстродействия synchronized-метода и synchronized-блока.


Сразу стоит сделать оговорку, что рассматриваться будет только быстродействие (время работы) этих двух реализаций синхронизаци.
В статье хабра, пункт 1, описывается неравнозначность Synchronized-метода и synchronized-блока (а здесь это описано чуть более подробно), обосновывается это неидентичным набором команд байт-кода.
На форумах и в обсуждении подобных статей периодически можно встретить мнения противоположные, что несмотря на различие в байт-коде оба случая исполняются равным количеством низкоуровневых действий процессора (ссылки нет).
Кто же прав? И влияет ли это на быстродействие кода?
Для проверки будем использовать jre 1.8.0_91 и следующий код:
public class SynchTimeTest {
    private static volatile long s = Long.MIN_VALUE;
    public static void main(String[] args) {
        int count = 100000000;
        long timeStarted = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            //место подстановки вызова проверяемого метода
        }
        System.out.println(System.currentTimeMillis() - timeStarted);
    }
    //synchronized-метод
    private static synchronized void synchMethod() {
        s++;
    }
    //synchronized-блок
    private static void methodWithSynch() {
        synchronized (SynchTimeTest.class) {
            s++;
        }
    }
}

  • С целью уравнивания условий измерений времени работы для обоих методов будем запускать код каждый раз заново, подставляя в «место подстановки вызова проверяемого метода» собственно вызов,
  • чтобы не провоцировать вызов GC не будем создавать объектов, а лишь выполнять простые арифметические операции сложения (инкремент).

По результатам работы получены выборки по 11 элементов (каждый элемент представляет собой время работы в мс ) для void synchMethod() и void methodWithSynch() соответственно:
void synchMethod(): 12235 10526 10477 10643 10439 10752 10532 10457 10604 11040 10856
void methodWithSynch(): 10642 10641 10623 10517 10561 10373 10629 10497 10457 10594 10498
исходя из выборок построена диаграмма:

и выведены средние (арифметические) значения времени работы:
для void synchMethod(): 10778
для void methodWithSynch(): 10548

В то же время время работы первого запуска void synchMethod() выглядит стохастическим (имеется значительное по сравнению с остальными отклонение от среднего), и после исключения его из выборки получаем картину, в общем то не отличающуюся:
для void synchMethod(): 10632
для void methodWithSynch(): 10548

Обрастив оба метода дополнительными вычислениями существенных отличий не получаем:
private static synchronized void synchMethod() {
	int i = 1;
	i++;
	s++;
	i++;
}

private static void methodWithSynch() {
	int i = 1;
	i++;
	synchronized (SynchStaticTimeTest.class) {
		s++;
	}
	i++;
}

выборка для void synchMethod(): 10540 10534 10664 10811 10791 10836
выборка для void methodWithSynch(): 10375 10583 10295 10409 10421 10447
диаграмма:

средние (арифметические) значения времени работы:
для void synchMethod(): 10696
для void methodWithSynch(): 10421

Любой цикл имеет свои накладные расходы, посмотрим насколько сильно он влияет на наши выборки: запустим наш код вхолостую без вызова какого-либо метода:
for (int i = 0; i < count; i++) {}

выборка для холостого запуска: 106 116 115 123 151 133, среднее арифметическое: 124, диаграмма сравнения со значениями других выборок:

, а значит влияние накладных расходов на выборки незначительно.
Выводы:
  • с точки зрения производительности разницы между synchronized-методом и synchronized-блоком (почти) нет, однако лучше покрывать синхронизацией минимальное количество кода (читай — минимально необходимое) для уменьшения простоя системы при активном использовании разными потоками синхронизированных областей кода (закон Амдала).
  • с другой стороны сами по себе synchronized-блоки уменьшают читабельность кода, а также объявление метода как synchronized скажет больше, чем блок в его глубине.

Конструктивная критика, мнения и дополнения приветствуются.

2 комментария

Fry
  • Fry
  • +1
Чесно говоря не понимаю как можно мерять вызовы synchronized-блока или synchronized-метода не в многопоточном контексте (не из разных потоков). Суть эксперимента теряется.
Exidnus
С помощью JMH не хотите производительность померить?
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.