Понимание JVM (6): безопасность потоков и оптимизация блокировок

Java задняя часть JVM Безопасность

Как реализовать потокобезопасность

Синхронизация взаимного исключения

Взаимное исключение — это причина, а синхронизация — следствие, взаимное исключение — это метод, а синхронизация — цель.

синхронизированное ключевое слово

  • synchronizedКлючевые слова являются основным средством синхронизации для взаимного исключения. Он добавит 2 инструкции байт-кода до и после блока кода синхронизации после компиляции:monitorenterа такжеmonitorexit
  • Оба этих байт-кода требуютreferenceПараметр типа, указывающий объект для блокировки и разблокировки. Если в Java-программеsynchronizedУказан объектный параметр, т.е.reference; если не указано, основывается наsynchronizedЯвляется ли модификация методом экземпляра или методом класса, чтобы получить соответствующий экземпляр объекта или объект класса в качестве объекта блокировки.
  • воплощать в жизньmonitorenterинструкция, сначала попытайтесь получить блокировку объекта. Если объект не заблокирован или текущий поток уже владеет блокировкой этого объекта, увеличьте счетчик блокировки на 1;monitorexitСчетчик блокировки уменьшается на 1 при выполнении инструкции. Когда счетчик достигает 0, блокировка снимается. Если получение блокировки объекта не удается, текущий поток заблокируется и будет ждать, пока блокировка объекта не будет снята другим потоком.
  • synchronizedСинхронизированные блоки повторно входят в один и тот же поток и не блокируются.
  • Синхронизированный блок будет блокировать вход других потоков, которые следуют, пока поток, который вошел, не завершит выполнение.
  • Потоки Java сопоставляются с собственными потоками операционной системы. Если вы хотите заблокировать или разбудить поток, вам нужна операционная система для его завершения. Для этого требуется переход из пользовательского состояния в основное состояние, поэтому переходы между состояниями требуют много времени. обработки серверного времени, поэтому синхронизация — это тяжеловесная операция в языке Java. Однако виртуальная машина имеет некоторые меры оптимизации, такие как ожидание вращения.

ReentrantLock блокировка повторного входа

Блокировка повторного входа расположена по адресуjava.util.concurrentСумка. основное использование иsynchronizedАналогично, но код написан по-другому:synchronizedЭто реализация уровня родной грамматики.ReentrantLockэто уровень API, использующийlock()а такжеunlock()координация методаtry/finallyблок заявлений.

Блокировки с повторным входом имеют 3 дополнительные функции:

  • Ожидание может быть прервано: когда поток, удерживающий блокировку, не освобождает блокировку в течение длительного времени, ожидающий поток может отказаться от ожидания и вместо этого заняться другими делами. Возможность прерывания полезна для работы с синхронизированными блоками, выполнение которых занимает очень много времени.
  • Справедливые блокировки могут быть достигнуты: справедливые блокировки означают, что, когда несколько потоков ожидают одной и той же блокировки, они должны получить блокировки в том порядке, в котором они применяли блокировки; несправедливые блокировки не гарантируют этого. любой ожидающий Поток, удерживающий блокировку, имеет возможность получить блокировку.synchronizedБлокировка несправедлива,ReentrantLockТакже несправедливо по умолчанию, но честные блокировки могут потребоваться через конструктор с логическим значением.
  • Блокировка может связывать несколько условий:ReentrantLockОбъекты могут быть привязаны к нескольким объектам одновременноConditionобъект, находясь вsynchronized, объект блокировкиwait()а такжеnotify()илиnotifyAll()Метод может реализовывать неявное условие. Если он должен быть связан более чем с одним условием, необходимо добавить дополнительную блокировку иReentrantLockтогда вам не нужно этого делать, вам просто нужно позвонить несколько разnewCondition()метод.

Сравнение производительности

  • До JDK1.6 в многопоточной средеsynchronizedПропускная способность очень сильно падает по мере увеличения числа процессоров.
  • После JDK1.6 виртуальная машина была оптимизирована, и производительность этих двух методов одинакова. Рекомендуется использовать в первую очередьsynchronizedСпособ.

неблокирующая синхронизация

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

Что касается решения проблемы:

  • Синхронизация с взаимным исключением является пессимистичной стратегией параллелизма: независимо от того, существует ли конфликт общих данных, будут приняты меры синхронизации (блокировка, преобразование пользовательского режима в режим ядра и т. д.).
  • Неблокирующая синхронизация — это оптимистичная стратегия параллелизма: она основана на обнаружении конфликтов. С точки зрения непрофессионала, сначала нужно выполнить код, если нет конфликта общих данных, он будет выполнен успешно; если есть конфликт общих данных, принять компенсирующие меры (например, повторить попытку до тех пор, пока она не увенчается успехом), эта стратегия не будет вызвать блокировку потока.

Работа КАС: Инструкция CAS требует трех операндов: местонахождение в памяти (которое можно просто понимать как адрес памяти переменной в Java, представленный V), старое ожидаемое значение (представленное A) и новое значение (представленное B ). Когда выполняется инструкция CAS, процессор обновляет значение V новым значением B тогда и только тогда, когда V совпадает со старым ожидаемым значением A, в противном случае он не выполняет обновление, а возвращает старое значение V независимо от того, значение V обновляется. Этот процесс является атомарной операцией.

Вопросы АБА: Если переменная V имеет значение A при первом чтении и проверяется, что она по-прежнему имеет значение A, когда она готова к присвоению, можем ли мы сказать, что ее значение не было изменено другими потоками? Если его значение когда-либо изменится на B в течение этого периода, а затем снова изменится на A, операция CAS примет это за тот факт, что оно никогда не менялось.

Нет схемы синхронизации

Если метод не использует общие данные, то ему не нужны никакие меры синхронизации для обеспечения корректности.

  • Реентерабельный код: этот вид кода также называется чистым кодом, который может быть прерван в любой момент выполнения кода для выполнения другого фрагмента кода (включая рекурсивный вызов самого себя), и после возврата управления исходная программа без каких-либо ошибок.
  • Локальное хранилище потоков: данные, требуемые в части кода, должны использоваться совместно с другими кодами, а видимый диапазон общих данных может быть ограничен одним и тем же потоком, поэтому нет необходимости в синхронизации, чтобы гарантировать отсутствие гонки данных. между нитями..
    • В языке Java, если переменная должна быть доступна нескольким потокам, она может быть объявлена ​​как "volatile" с использованием ключевого слова volatile; если переменная должна использоваться исключительно потоком, в Java нет __declspec(thread). аналогично тому, что в C++.Такие ключевые слова, но функция локального хранения потока все еще может быть реализована через класс java.lang.ThreadLocal. В объекте Thread каждого потока есть объект ThreadLocalMap. Этот объект хранит набор пар значений KV с ThreadLocal.threadLocalHashCode в качестве ключа и локальными переменными потока в качестве значения. Объект ThreadLocal является записью доступа к ThreadLocalMap текущего Все объекты ThreadLocal содержат уникальное значение threadLocalHashCode, которое можно использовать для извлечения соответствующей локальной переменной потока в паре значений KV потока.

оптимизация блокировки

Адаптивный спиннинг

Когда поток заблокирован, ожидающий поток не уступает процессорному времени, а выполняет спин (обычно пустой цикл), который называется спин-блокировкой.

Хотя ожидание вращения само по себе позволяет избежать накладных расходов на переключение потоков, оно занимает процессорное время.Поэтому, если блокировка занята на короткое время, эффект ожидания вращения очень хороший.Наоборот, если блокировка занята на короткое время time Если оно очень длинное, вращающиеся потоки будут напрасно потреблять ресурсы процессора, что приведет к потере производительности.

Поэтому время ожидания спина должно иметь определенный предел. Если блокировка не была успешно получена после более чем ограниченного количества вращений, следует использовать традиционный метод для приостановки потока. По умолчанию количество спинов равно 10, пользователь может использовать параметр-XX:PreBlockSpinизменить.

В JDK1.6 представлены адаптивные спин-блокировки. Адаптивный означает, что время вращения больше не является фиксированным, а определяется предыдущим временем вращения того же замка и состоянием владельца замка. Например, замок был получен при предыдущем вращении 3 раза, а следующая виртуальная возможность позволяет ему вращать большее количество раз, чтобы получить замок. Если блокировка редко удается успешно получить с помощью вращения, то процесс вращения пропускается, когда эта ситуация возникает позже.

Устранение блокировки

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

Огрубление блокировки

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

Однако, если серия операций часто блокирует и разблокирует один и тот же объект или если блокирующая операция повторно используется в теле, это будет потреблять производительность.В это время виртуальная машина расширит диапазон блокировки.

Легкая блокировка

Облегченная блокировка — это новый механизм блокировки, добавленный в JDK 1.6. Его функция заключается в снижении потребления производительности, вызванного традиционной тяжеловесной блокировкой с использованием мьютекса операционной системы без многопоточной конкуренции.

Заголовок объекта виртуальной машины HotSpot разделен на две части информации. Первая часть используется для хранения данных времени выполнения самого объекта. Эта часть называется为Mark Word. Существует также раздел, в котором хранятся указатели на данные типа объекта в области метода.

замок

Когда код входит в блок синхронизации, если объект синхронизации не заблокирован (флаг блокировки равен «01»), виртуальная машина сначала создаст пространство с именем Lock Record в кадре стека текущего потока. Используется для хранения копии текущего Mark Word объекта замка (официал добавил к этой копии префикс Displaced, то есть Displaced Mark Word). Затем виртуальная машина попытается обновить слово метки объекта до указателя на запись блокировки, используя операцию CAS. Если операция обновления прошла успешно, то поток владеет блокировкой объекта, а флаг блокировки объекта Mark Word (последние 2 бита Mark Word) будет изменен на «00», что означает, что объект в облегченном состоянии замка. Если операция обновления не удалась, виртуальная машина сначала проверит, указывает ли Mark Word объекта на кадр стека текущего потока.Если это означает, что текущий поток уже владеет блокировкой этого объекта, он может напрямую войти в синхронизацию block для продолжения выполнения, в противном случае это означает, что объект блокировки был вытеснен другим потоком. Если более двух потоков борются за одну и ту же блокировку, облегченная блокировка больше недействительна.Чтобы превратиться в тяжеловесную блокировку, значение состояния флага блокировки становится равным "10", а то, что хранится в Mark Word, указывает на Тяжеловесная блокировка.Указатель на блокировку (мьютекс) и поток, ожидающий блокировку, также перейдут в состояние блокировки.

разблокировать

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

представление

Когда нет конкуренции замков, облегченные блокировки используют операции CAS, чтобы заменить накладные расходы на мьютекс, и производительность выше. Когда существует конкуренция блокировок, в дополнение к накладным расходам мьютекса возникают также накладные расходы на операцию CAS, поэтому производительность низкая. Однако в целом на протяжении всего цикла синхронизации конфликтов нет», что является эмпирическими данными.

Предвзятая блокировка

Предвзятая блокировка также представляет собой оптимизацию блокировки, представленную в JDK 1.6. Ее цель — устранить примитив синхронизации данных в случае отсутствия конкуренции и еще больше повысить производительность программы. Если упрощенная блокировка заключается в использовании операции CAS для устранения мьютекса, используемого для синхронизации без конкуренции, то блокировка со смещением предназначена для устранения всей синхронизации без конкуренции, и даже операция CAS не выполняется.

Когда объект блокировки будет получен потоком в первый раз, виртуальная машина установит флаг в заголовке объекта на «01», что является режимом смещения. В то же время операция CAS используется для записи идентификатора потока, получившего блокировку, в Mark Word объекта.Если операция CAS прошла успешно, каждый раз, когда поток, удерживающий смещенную блокировку, входит в блок синхронизации, связанный с блокировка, виртуальная машина больше не может делать синхронизации. Режим смещения завершается, когда другой поток пытается получить блокировку.

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