Мне кажется, что ключевое слово synchronized хуже из-за риска блокировки блокировки (что может привести к переключению контекста потока и т. Д.)
Да, в общем случае вы правы. Параллелизм Java на практике обсуждает это в разделе 15.3.2:
[...] при высоких уровнях конкуренции блокировка имеет тенденцию превосходить атомарные переменные, но при более реалистичных уровнях конкуренциипеременные превосходят блокировки.Это связано с тем, что блокировка реагирует на конфликты путем приостановки потоков, снижения нагрузки на процессор и синхронизации трафика на шине общей памяти.(Это похоже на то, как блокирование производителей в схеме «производитель-потребитель» снижает нагрузку на потребителей и, тем самым, позволяет им наверстать упущенное.) С другой стороны, при использовании атомарных переменных управление конфликтами отодвигается обратно в вызывающий класс.Как и большинство основанных на CAS алгоритмов, AtomicPseudoRandom
реагирует на конфликты, немедленно повторяя попытку, что обычно является правильным подходом, но в среде с высоким уровнем конкуренции просто создает больше конфликтов.
Прежде чем мы осудим AtomicPseudoRandom
как плохойзаписанные или атомарные переменные в качестве плохого выбора по сравнению с блокировками, мы должны понимать, что уровень конкуренции на рисунке 15.1 нереально высок: ни одна реальная программа не делает ничего, кроме как борется за блокировку или атомарную переменную.На практике атомика имеет тенденцию масштабироваться лучше, чем блокировки, потому что атомики эффективнее справляются с типичными уровнями конкуренции.
Изменение производительности между замками и атомами на разных уровнях конкуренции иллюстрирует сильные и слабые стороны каждого из них.При низком и умеренном конфликте атомы обеспечивают лучшую масштабируемость;с высокой конкуренцией, замки предлагают лучшее предотвращение конкуренции.(Алгоритмы на основе CAS также превосходят алгоритмы на основе блокировок в системах с одним ЦП, поскольку CAS всегда успешно работает в системе с одним ЦП, за исключением маловероятного случая, когда поток прерывается в середине операции чтения-изменения-записи.)
(На рисунках, на которые ссылается текст, рисунок 15.1 показывает, что производительность AtomicInteger и ReentrantLock более или менее одинакова при высоком уровне конкуренции, а рисунок 15.2 показывает, что при умеренном уровне конкуренции первыйвыигрывает у последнего в 2-3 раза.)
Обновление: для неблокирующих алгоритмов
Как отмечали другие, неблокирующие алгоритмы, хотя и потенциально более быстрые, являются более сложными, поэтому их труднее получитьправо.Подсказка из раздела 15.4 JCiA:
Хорошие неблокирующие алгоритмы известны многими распространенными структурами данных, включая стеки, очереди, приоритетные очереди и хеш-таблицы, хотя разработка новых является задачей, которую лучше оставитьЭксперты.
Неблокирующие алгоритмы значительно сложнее, чем их эквиваленты на основе блокировок.Ключом к созданию неблокирующих алгоритмов является выяснение того, как ограничить область атомарных изменений одной переменной при сохранении согласованности данных.В связанных классах коллекций, таких как очереди, иногда вы можете избежать выражений преобразований состояний в виде изменений отдельных ссылок и использовать AtomicReference
для представления каждой ссылки, которая должна обновляться атомарно.