Параллелизм очень важен для разработки надежных, масштабируемых приложений, которые могут выполнять несколько одновременных операций. Однако за это приходится платить за синхронизацию. Это влечет за собой затраты на производительность из-за сопутствующих накладных расходов на получение и освобождение блокировок. Чтобы снизить эти затраты на производительность, в JVM было включено несколько оптимизаций в нескольких вариантах, таких как смещенная блокировка, устранение блокировок, укрупнение блокировок и понятие облегченных и тяжеловесных блокировок.
В этой статье мы рассмотрим эти оптимизации более подробно и рассмотрим, как они улучшают синхронизацию в многопоточных приложениях Java.
Основы блокировки Java
В Java синхронизация блоков или методов гарантирует, что только один поток может одновременно выполнять критический раздел кода. Это особенно важно при рассмотрении совместного использования ресурсов в многопоточной среде. Java реализует это, полагаясь на внутренние блокировки, иногда их называют мониторами, связанными с объектами или классами, которые помогают управлять доступом к потокам с помощью синхронизированных блоков.
Хотя синхронизация необходима для обеспечения безопасности потоков, она может быть весьма дорогостоящей, если конкуренция незначительна или полностью отсутствует. Именно здесь на сцену выходит оптимизация JVM. Таким образом, это снижает стоимость блокировки и повышает общую производительность.
1. Смещенная блокировка
Что такое предвзятая блокировка?
Смещенная блокировка — это оптимизация, направленная на снижение затрат на получение блокировки. Он оптимизирован для снижения затрат на получение блокировок, в которых доминирует один поток или в большинстве случаев доступ к ним осуществляется одним потоком. Такие программы часто получают и снимают блокировки в одном и том же потоке без конкуренции со стороны других потоков. JVM может распознать этот шаблон и сместить блокировку на этот конкретный поток; последующее получение блокировки происходит практически бесплатно.
Как работает предвзятая блокировка?
Если включена смещенная блокировка, то в первый раз, когда поток получает блокировку, эта блокировка смещается в сторону этого потока. Идентификатор потока записывается в заголовке объекта блокировки, и последующее получение блокировки этим потоком не требует какой-либо синхронизации — они просто проверяют, смещена ли блокировка в сторону текущего потока, что является очень быстрой неблокирующей операцией. .
Если другой поток пытается получить блокировку, то смещение отменяется, и JVM возвращается к стандартному механизму несмещенной блокировки. На этом этапе это теперь стандартная блокировка, и второму потоку придется получить ее посредством стандартного процесса блокировки.
Преимущества смещенной блокировки
Производительность: Получение того же потока на смещенной блокировке — это почти бесплатное получение блокировки.
Следовательно, обработка конфликтов не требуется, поскольку другие потоки не имеют возможности участвовать в получении блокировки.
Меньшие издержки: Состояние блокировки или метаданные, связанные с синхронизацией, не требуют изменения, за исключением случаев конфликта.
Когда используется смещенная блокировка?
Смещенная блокировка полезна в приложениях, где блокировки в основном доступны одному и тому же потоку, например, в однопоточных приложениях или в приложениях с низким уровнем конфликтов блокировок при многопоточности. В большинстве JVM он включен по умолчанию.
Как отключить предвзятую блокировку
Смещенная блокировка включена по умолчанию, но ее также можно отключить с помощью флага JVM, как показано ниже:
-XX:-UseBiasedLocking
2. Устранение блокировки
Что такое устранение блокировки?
Устранение блокировок — это очень мощная оптимизация, при которой JVM полностью устраняет ненужную синхронизацию (блокировки). Он проверит код на наличие каких-либо возможностей во время JIT-компиляции и обнаружит, что синхронизация не требуется. Обычно это происходит, когда к блокировке обращался только один поток или объект, который JVM будет использовать для синхронизации, не использует один и тот же объект в разных потоках. Как только JVM сочтет, что это больше не требуется, она снимает блокировку.
Как работает устранение блокировок?
На этапе escape-анализа JIT-компиляции JVM проверяет, ограничен ли объект одним потоком или используется только в локальном контексте. Если синхронизацию этого объекта можно удалить, поскольку объект не выходит за пределы потока, который его создал, то так и будет.
Например, если объект создается и используется полностью внутри метода (и не разделяется между потоками), JVM понимает, что никакой другой поток не может получить доступ к объекту, и, таким образом, вся синхронизация является избыточной. В таком случае JIT-компилятор просто полностью устраняет блокировку.
Нулевые затраты на блокировку: Устранение ненужной синхронизации также не позволит JVM оплачивать затраты на получение и снятие блокировок.
Более высокая пропускная способность: Мертвая синхронизация иногда может привести к более высокой пропускной способности приложения, особенно если код содержит много синхронизированных блоков.
Взгляните на этот фрагмент кода:
public void someMethod() { StringBuilder sb = new StringBuilder(); synchronized (sb) { sb.append("Hello"); sb.append("World"); } }
В этом случае синхронизация на sb не требуется, поскольку StringBuilder используется только внутри someMethod и не используется совместно другими потоками. Посмотрев на это, JVM может выполнить escape-анализ, чтобы снять блокировку.
3. Огрубление замка
Что такое укрупнение блокировки?
Укрупнение блокировки — это оптимизация, при которой JVM расширяет область действия блокировки, чтобы охватить больше фрагментов кода, вместо того, чтобы постоянно получать и снимать блокировку в циклах или небольших участках кода.
Работы по укрупнению замков
Если JVM обнаружит, что тесный цикл или несколько соседних блоков кода слишком часто захватывают и освобождают блокировку, она может сделать блокировку более грубой, вынеся блокировку за пределы цикла или по нескольким блокам кода. Это делает многократное получение и освобождение блокировки без блокировки дорогостоящим и позволяет потоку удерживать блокировку для большего количества итераций.
Пример кода: усиление блокировки
Рассмотрите этот фрагмент кода:
for (int i = 0; iУкрупнение блокировки выводит получение блокировки за пределы цикла, поэтому поток получает блокировку только один раз:
synchronized (lock) { for (int i = 0; iJVM может значительно повысить производительность, избегая большего количества захватов и снятий блокировки.
Преимущества укрупнения замков
Меньше свободы накладных расходов на блокировку: Укрупнение позволяет избежать получения и освобождения блокировок, особенно в коде горячей точки, например в циклах, которые повторяются тысячи раз.
Улучшение производительности:
Блокировка на более длительный период повышает производительность по сравнению со сценарием, в котором без блокировки такая блокировка может быть получена и снята несколько раз.4. Легкие и тяжелые замки
JVM использует два разных метода блокировки, основанных на степени конкуренции между потоками. К таким методам относятся легкие замки и тяжелые замки.
Легкий замок
Облегченная блокировка происходит при отсутствии конфликтной блокировки, то есть только один поток пытается получить эту блокировку. В таких сценариях JVM оптимизирует получение с помощью операции CAS при попытке получить блокировку, что может произойти без тяжелой синхронизации.
Тяжелая блокировка
В случае, если несколько потоков хотят получить одну и ту же блокировку; то есть есть разногласия, JVM переводит это в тяжеловесную блокировку. Это потребует блокировки потоков на уровне ОС и управления ими с помощью примитивов синхронизации на уровне ОС. Тяжелые блокировки работают медленнее, поскольку они фактически требуют от ОС выполнения переключения контекста, а также управления потоками.
Эскалация блокировки
Если возникает конфликт при легкой блокировке, JVM может перевести его на более серьезную блокировку. Эскалация здесь означает переключение с быстрой блокировки на уровне пользователя на более дорогую блокировку на уровне ОС, которая включает блокировку потоков.
Преимущества легких замков
Быстрое получение блокировки: При отсутствии конфликтов легкие блокировки работают намного быстрее, чем тяжелые блокировки, поскольку они избегают синхронизации на уровне ОС.
Уменьшение блокировки: При отсутствии конфликтов потоки не блокируются и увеличиваются линейно с меньшей задержкой.
Недостатки тяжелых замков
Накладные расходы на производительность: тяжелые блокировки влекут за собой затраты на блокировку потоков, переключение контекста и пробуждение потоков, что приводит к снижению производительности в режимах с очень высокой конкуренцией.
Все эти оптимизации помогают JVM повысить производительность многопоточных приложений, поэтому разработчики теперь могут писать безопасный параллельный код, не жертвуя при этом значительными затратами на синхронизацию. Понимание этих оптимизаций может помочь разработчикам разрабатывать более эффективные системы, особенно в случаях, когда блокировка приводит к снижению производительности.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3