Могут ли синхронизированные блоки быть быстрее, чем Atomics?

Предположим, две следующие реализации счетчика:

class Counter {
  private final AtomicInteger atomic = new AtomicInteger(0);
  private int i = 0;

  public void incrementAtomic() {
    atomic.incrementAndGet();
  }

  public synchronized void increment() {
    i++;
  }
}

На первый взгляд атомарность должна быть быстрее и масштабируемее. И они есть, я верю. Но всегда ли они быстрее synchronized блоков? Или существуют ситуации, когда это правило нарушается (например, SMP/однопроцессорная машина, разные процессоры ISA, ОС и т. д.)?


person Denis Bazhenov    schedule 18.07.2012    source источник


Ответы (5)


incrementAndGet вполне может быть реализован как цикл CAS. В ситуациях с высокой степенью удовлетворенности, которые могут привести к сбою n-1 ваших потоков, что приведет к проблеме O(n) для n потоков.

(Для @Geek:

Обычно getAndIncrement может быть реализован примерно так:

 int old;
 do {
     old = value;
 } while (!compareAndSet(value, old, old+1));
 return old;

Представьте, что у вас есть n потоков, выполняющих этот код на одном и том же атоме, и они идут в ногу друг с другом. Первая итерация kn работает. Только один CAS будет успешным. Другие n-1 потоки будут повторять упражнение, пока не останется только один. Таким образом, общая работа составляет O(n^2) (в худшем случае) вместо O(n). )

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

person Tom Hawtin - tackline    schedule 19.07.2012
comment
не могли бы вы уточнить свой первый абзац? - person Geek; 19.07.2012

Или существуют ситуации, когда это правило нарушается (например, SMP/однопроцессорная машина, разные процессоры ISA, ОС и т. д.)?

Я не знаю ни одного. (И я готов быть поправленным... если кто-то знает конкретный контрпример.)

Однако (и это моя основная мысль) нет теоретической причины, по которой нельзя было бы иметь аппаратную архитектуру или плохо реализованную JVM, в которой synchronized имеет такую ​​же скорость или быстрее, чем типы atomic. (Относительная скорость этих двух форм синхронизации зависит от реализации и может быть определена количественно только для существующих реализаций.)

И, конечно же, это не означает, что вы никогда не должны использовать synchronized. Конструкция synchronized имеет много вариантов использования, которые атомарные классы не учитывают.

person Stephen C    schedule 19.07.2012
comment
Да, атомарность гарантирует видимость памяти, но не взаимное выполнение. - person Denis Bazhenov; 19.07.2012

Это зависит от реализации, поэтому в конечном итоге вам нужно будет сравнить вашу конкретную платформу/JVM/конфигурацию.

Сказав это, атомарность должна всегда быть быстрее по следующим причинам:

  • Atomics разработана таким образом, чтобы JVM могла использовать атомарные машинные инструкции, которые являются самыми быстрыми атомарными операциями, которые вы можете получить для большинства платформ.
  • synchronized использует относительно тяжелые схемы блокировки с объектами монитора, предназначенные для защиты потенциально больших блоков кода. Эта форма блокировки по своей сути более сложна, чем атомарные операции, поэтому можно ожидать, что она будет иметь более высокую стоимость во время выполнения.
person mikera    schedule 19.07.2012

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

person Geek    schedule 19.07.2012

атомарные переменные всегда будут быстрее.

вы можете видеть, что пакет java.util.concurrent всегда использует атомарные переменные, а не синхронизированные блоки.

person tommo    schedule 18.07.2012
comment
Второе утверждение, хотя и верно, ничего не доказывает. Фактически, это только подразумевает, что атомарные типы быстрее в среднем... для тех конкретных вариантов использования... на ISA/платформах, которые поддерживает Oracle. - person Stephen C; 19.07.2012
comment
Почему? Если ваше второе предложение должно указывать причину, оно этого не делает. Заявленная цель параллельного пакета — нигде не использовать синхронизацию. - person user207421; 19.07.2012
comment
в очень немногих случаях оптимистической блокировки атомарные переменные медленнее, и причина, по которой я упомянул об этом, заключалась в том, чтобы показать, что они являются предпочтительным решением и, вероятно, лучшим решением в большинстве случаев. - person tommo; 19.07.2012
comment
Вы до сих пор не сказали, почему; и то, что параллельный пакет делает внутри, не является свидетельством производительности; это только показывает, что параллельный пакет соответствует целям проектирования. Было бы удивительно, если бы он не использовал свои собственные классы. - person user207421; 19.07.2012