Сокрушительный параллелизм (7): Углубленный анализ принципа синхронизированного

Java задняя часть JVM

0 Предисловие

Я помню, когда я начал изучать Java, я использовал синхронизацию всякий раз, когда сталкивался с многопоточной ситуацией. По сравнению с нами в то время синхронизация была настолько волшебной и мощной. В то время мы дали ей имя «синхронизация», которое также стал для нас проблемой для решения многих проблем.Проверенное и верное средство для потоковых ситуаций. но,По мере обучения мы знаем, что synchronized — это тяжеловесная блокировка до JDK1.5, по сравнению с j.u.c.Lock она будет настолько громоздкой, что мы думаем, что она не так эффективна, и постепенно отказываемся от нее..

но,С различными оптимизациями синхронизированного в Javs SE 1.6 синхронизированное не будет таким тяжелым.. Давайте рассмотрим основное использование, механизм реализации синхронизированного, как его оптимизирует Java, механизм оптимизации блокировок, структуру хранения блокировок и другие процессы обновления.

1 Основное использование

Синхронизация — это один из наиболее распространенных способов решения проблем параллелизма в Java, а также самый простой способ.Есть три основные функции Synchronized:

  1. атомарность: доступ к коду синхронизации для обеспечения взаимного исключения потоков;
  2. видимость: Чтобы гарантировать, что модификация общих переменных может быть замечена во времени, по сути, через модель памяти Java.«Перед разблокировкой переменной ее необходимо синхронизировать с основной памятью, если переменная заблокирована, то значение переменной в рабочей памяти будет очищено. memory Операция загрузки или операция присваивания инициализирует значение переменной"гарантировать;
  3. упорядоченность: эффективно решить проблему переупорядочивания, т.е.«Происходит операция разблокировки — до того, как следует операция блокировки того же замка»;

Грамматически говоря,Synchronized может рассматривать любой ненулевой объект как «замок»., в реализации HotSpot JVM блокировка имеет специальное имя:Объектный монитор.

Synchronized имеет в общей сложности три использования:

  1. Когда синхронизация действует на методы экземпляра,Блокировка монитора (монитор) — это экземпляр объекта (этот);
  2. Когда синхронизация действует на статические методы,Блокировка монитора (монитор) является экземпляром класса объекта, поскольку данные класса существуют в постоянном поколении,Поэтому блокировка статического метода эквивалентна глобальной блокировке класса;
  3. Когда синхронизация действует на экземпляр объекта,Блокировка монитора (монитор) — это экземпляр объекта, заключенный в круглые скобки.;

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

Метод синхронизации подкласса вызывает метод синхронизации родительского класса.Если нет возможности повторного входа, произойдет взаимоблокировка;

2 Принцип синхронизации

Синхронизация данных должна опираться на блокировки, а от кого зависит синхронизация блокировок?Ответ, данный synchronized, заключается в том, чтобы полагаться на JVM на программном уровне, в то время как ответ, данный j.u.c.Lock, заключается в том, чтобы полагаться на специальные инструкции ЦП на аппаратном уровне..

Когда поток обращается к синхронизированному блоку кода,Во-первых, для выполнения синхронного кода необходимо получить блокировку, и блокировку необходимо снять при выходе или выдаче исключения., то как он реализует этот механизм? Давайте сначала посмотрим на простой код:

package com.paddx.test.concurrent;

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

Посмотреть результат после декомпиляции:

反编译结果

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

    1. Если счетчик записей монитора равен 0, поток входит в монитор, а затем устанавливает номер записи равным 1, и поток становится владельцем монитора;
    2. Если поток уже владеет монитором, просто повторно зайти, тогда количество заходов в монитор увеличивается на 1;
    3. Если другие потоки уже занимают монитор, поток переходит в состояние блокировки до тех пор, пока счетчик записей монитора не станет равным 0, а затем повторяет попытку получить право собственности на монитор;
  2. monitorexit: Поток, выполняющий monitorexit, должен быть владельцем монитора, соответствующего объектной ссылке.Когда инструкция выполняется, номер записи монитора уменьшается на 1. Если номер записи равен 0 после уменьшения на 1, поток выходит из монитора и больше не является владельцем монитора.. Другие потоки, заблокированные этим монитором, могут попытаться завладеть этим монитором.

    Инструкция monitorexit появляется дважды, первый раз — снять блокировку для синхронного нормального выхода, второй раз — снять блокировку для асинхронного выхода.;

Через два приведенных выше абзаца мы должны ясно увидеть принцип реализации синхронизированного,Базовая семантика Synchronized осуществляется через объект монитора Фактически, такие методы, как ожидание/уведомление, также зависят от объекта монитора., поэтому такие методы, как ожидание/уведомление, можно вызывать только в синхронизированных блоках или методах,В противном случае будет выброшена причина исключения java.lang.IllegalMonitorStateException..

Давайте еще раз посмотрим на метод синхронизации:

package com.paddx.test.concurrent;

public class SynchronizedMethod {
    public synchronized void method() {
        System.out.println("Hello World!");
    }
}

Посмотреть результат после декомпиляции:

反编译结果

По результатам компиляции синхронизация метода не проходит инструкцияmonitorenterиmonitorexit(теоретически этого можно добиться и этими двумя инструкциями), но по сравнению с обычными методами в пуле констант больше пулов констант.ACC_SYNCHRONIZEDИдентификатор.JVM реализует синхронизацию методов на основе этого идентификатора.:

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

По сути, между двумя методами синхронизации нет разницы, за исключением того, что синхронизация метода реализована неявным образом, без необходимости использования байт-кода.Выполнение двух инструкций реализуется JVM путем вызова примитивного взаимного исключения мьютекса операционной системы, а заблокированный поток будет приостановлен и ожидает повторного планирования., что приведет к переключению между двумя состояниями «режим пользователя и режим ядра», что сильно повлияет на производительность.

3 концепции синхронизации

3.1 Заголовок объекта Java

В JVM расположение объектов в памяти разделено на три области:Заголовок объекта, данные экземпляра и заполнение выравнивания. Как показано ниже:

  1. данные экземпляра: Хранит информацию об атрибутах класса, включая информацию об атрибутах родительского класса;
  2. Выровнять отступы: из-за требований виртуальной машиныНачальный адрес объекта должен быть целым числом, кратным 8 байтам.. Данные заполнения не должны существовать, только для выравнивания байтов;
  3. заголовок объекта:Заголовки объектов Java обычно занимают 2 машинных кода.(В 32-битной виртуальной машине 1 машинный код равен 4 байтам, что составляет 32 бита, а в 64-битной виртуальной машине 1 машинный код равен 8 байтам, что составляет 64 бита), ноЕсли объект является типом массива, требуются 3 машинных кода, поскольку виртуальная машина JVM может определить размер объекта Java с помощью информации метаданных объекта Java., но размер массива нельзя подтвердить из метаданных массива, поэтому для записи длины массива используется блок.

Блокировка, используемая Synchronized, хранится в заголовке объекта Java., так что же такое заголовок объекта Java? Заголовок объекта виртуальной машины Hotspot в основном включает две части данных:Mark Word (отметить поле), Class Pointer (указатель типа). вУказатель класса — это указатель объекта на его метаданные класса., виртуальная машина использует этот указатель, чтобы определить, экземпляром какого класса является объект,Mark Word используется для хранения данных времени выполнения самого объекта, что является ключом к реализации облегченных блокировок и смещенных блокировок.. Конкретная структура заголовка объекта Java описывается следующим образом:

Java对象头结构组成

Mark Word используется для хранения данных времени выполнения самого объекта, таких как: хэш-код (HashCode), возраст генерации GC, флаг состояния блокировки, удерживаемая блокировка потока, смещенный идентификатор потока, смещенная отметка времени и т. д.. На следующем рисунке показан заголовок объекта Java.Структура хранения части Mark Word в незаблокированном состоянии(32-битная виртуальная машина):

Mark Word存储结构

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

Mark Word可能存储4种数据

Под 64-битной виртуальной машинойМарк Word имеет размер 64 бита., а его структура хранения выглядит следующим образом:

64位Mark Word存储结构

Последние два бита заголовка объекта хранят флаг блокировки,01 — исходное состояние, разблокировано, в заголовке объекта хранится хэш-код самого объекта.При разных уровнях блокировки в заголовке объекта хранится разное содержимое.Смещенная блокировка сохраняет идентификатор потока, который в данный момент занимает этот объект.;в то время как облегченный хранит указатели на блокировку записей в стеке потоков. Отсюда мы можем видеть, что "замок",Может быть запись блокировки + указатель ссылки в заголовке объекта(При оценке того, есть ли у потока блокировка, сравните адрес записи блокировки потока с адресом указателя в заголовке объекта),Это также может быть идентификатор потока в заголовке объекта.(Сравните идентификатор потока с идентификатором потока, хранящимся в заголовке объекта, при оценке того, владеет ли поток блокировкой).

HotSpot虚拟机对象头Mark Word

3.2 Отметить слово в заголовке объекта и заблокировать запись в потоке

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

Lock Record — это структура данных, закрытая для потока., каждый поток имеет список доступных записей блокировки, а также глобальный список доступных записей блокировки.Каждое слово метки заблокированного объекта будет связано с записью блокировки (слово блокировки в слове маркировки заголовка объекта указывает на начальный адрес записи блокировки), и в записи блокировки есть поле «Владелец» для хранения уникального идентификатора объекта. поток, которому принадлежит блокировка (илиobject mark word), указывая на то, что блокировка занята этим потоком. На следующем рисунке показана внутренняя структура Lock Record:

Lock Record описывать
Owner Первоначально NULL означает, что ни один поток в настоящее время не владеет записью монитора,Когда поток успешно владеет блокировкой, сохраните уникальный идентификатор потока., которому при снятии блокировки присваивается значение NULL;
EntryQ Свяжите системный мьютекс (семафор),Блокировать все потоки, которые не могут заблокировать запись монитора;
RcThis Указывает количество всех потоков, заблокированных или ожидающих записи монитора.;
Nest используется для реализацииКоличество повторных блокировок;
HashCode Сохраните значение HashCode, скопированное из заголовка объекта (и, возможно, возраст GC).
Candidate Он используется, чтобы избежать ненужной блокировки или ожидания пробуждения потоков, поскольку только один поток может успешно владеть блокировкой в ​​каждый момент времени.Если предыдущий поток, освободивший блокировку, разбудит все потоки, которые блокируются или ожидают, это вызовет ненужные переключения контекста (от блокировки до готовности, а затем снова блокировка из-за неудачной блокировки блокировки), что приводит к серьезному снижению производительности.Кандидат имеет только два возможных значения: 0 означает, что нет потока для пробуждения, 1 означает, что нужно разбудить поток-преемник для борьбы за блокировку..

3.3 Монитор

Любой объект имеет связанный с ним монитор, и когда монитор удерживается, он будет заблокирован.. Реализация Synchronized в JVMСинхронизация методов и синхронизация блоков кода на основе входа и выхода из объекта Monitor, хотя конкретные детали реализации различаются, все они могут быть реализованы с помощью пар инструкций MonitorEnter и MonitorExit.

  1. Инструкция MonitorEnter: вставляется в начало блока синхронизированного кода., когда код выполнит инструкцию, он попытается получить право собственности на объект Monitor, то есть попытается получить блокировку объекта;
  2. Директива MonitorExit: вставлена ​​в конце метода и в исключении, JVM гарантирует, что каждый MonitorEnter должен иметь соответствующий MonitorExit;

Так что же такое монитор? можно понимать какинструмент синхронизации, который также может быть описан какмеханизм синхронизации, что обычноописывается как объект.

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

То есть обычно вызывается блокировка объекта Synchronized.Флаг блокировки MarkWord равен 10, а указатель указывает на начальный адрес объекта Monitor.. В виртуальной машине Java (HotSpot)Монитор реализован ObjectMonitor, его основная структура данных выглядит следующим образом (находится в файле ObjectMonitor.hpp исходного кода виртуальной машины HotSpot, реализованной на C++):

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // 记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; // 处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

В ObjectMonitor есть две очереди,_WaitSet и _EntryList, используемый для хранения списка объектов ObjectWaiter (Каждый поток, ожидающий блокировки, будет инкапсулирован как объект ObjectWaiter.),_owner указывает на поток, содержащий объект ObjectMonitor, когда несколько потоков одновременно обращаются к фрагменту синхронизированного кода:

  1. Во-первых, он войдет в коллекцию _EntryList,Когда поток получает монитор объекта, он входит в область _Owner и устанавливает переменную владельца в мониторе на текущий поток и увеличивает счетчик счетчика в мониторе на 1.;
  2. Если поток вызывает метод wait(),Текущий монитор будет освобожден, переменная владельца будет восстановлена ​​до нуля, счетчик будет уменьшен на 1, а поток войдет в коллекцию WaitSet и будет ждать пробуждения.;
  3. Если текущий поток завершает выполнение,Он также освобождает монитор (блокировка) и сбрасывает значение счетчика, чтобы другие потоки могли войти для захвата монитора (блокировка).;

в то же время,Объект Monitor существует в заголовке объекта Mark Word каждого объекта Java (указывающего на сохраненный указатель), и синхронизированная блокировка получает блокировку таким образом., поэтому любой объект в Java можно использовать в качестве замка,В то же время такие методы, как notify/notifyAll/wait, будут использовать объект блокировки Monitor, поэтому его необходимо использовать в блоке кода синхронизации..

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

Когда необходимо сотрудничество?Например:

Один поток записывает данные в буфер, а другой поток читает данные из буфера. Если читающий поток обнаружит, что буфер пуст, он будет ждать. Когда записывающий поток записывает данные в буфер, он разбудит читающий поток.Здесь читать темы и писать темы - это отношения. JVM ждет себя через метод ожидания класса ожидания, позвонив методу ожидания, нить будет отпустить монитор, который он удерживает, пока другие потоки не будут уведомить его, чтобы иметь возможность выполнить. Нить вызывает метод уведомления, чтобы уведомить ожидающий нить, это нить ожидания не будет выполнена немедленно, но уведомить монитор отпуска потока, он повторно приобретает монитор, чтобы иметь возможность выполнять. Если монитор, который просто разбирается, выгружен на монитор другими потоками, то этот поток будет продолжать ждать. Метод NotibleAll в классе объекта может решить эту проблему, которая может проснуться все потоки ожидания, и всегда есть выполнение потока.

Как показано на рисунке выше, поток входит в Entry Set (область входа) через шлюз 1. Если в области входа нет ни одного потока, ожидающего в области входа, то этот поток получит монитор, чтобы стать его владельцем, а затем выполнит код в районе монитора. Если в области входа есть другие потоки, ожидающие выполнения, новый поток также будет ожидать вместе с этими потоками. В процессе удержания монитора у нити есть два варианта,Один из них — это код, который обычно выполняет область монитора., отпустите монитор, выйдите из монитора через дверь 5;Также можно дождаться появления определенного условия, поэтому он пройдет через ворота 3 в набор ожидания (зону ожидания), чтобы отдохнуть, пока не будут выполнены соответствующие условия, а затем войдет через ворота 4, чтобы снова получить монитор и выполнить его снова.

Уведомление:

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

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

Недавно добавленные атомарные операции CAS для современных операционных систем были введены из JDK5 (Ключевое слово synchronized не оптимизировано в JDK5, но отражено в J.U.C, поэтому параллельный пакет в этой версии имеет лучшую производительность.), начиная с JDK6, механизм реализации synchronized был значительно скорректирован.В дополнение к вращению CAS, введенному JDK5, были добавлены стратегии оптимизации, такие как адаптивное вращение CAS, устранение блокировки, огрубление блокировки, смещенная блокировка и облегченная блокировка.. Поскольку оптимизация этого ключевого слова значительно повышает производительность и в то же время имеет четкую семантику, простую работу и отсутствие необходимости закрывать его вручную, рекомендуется использовать это ключевое слово как можно чаще, и еще есть место для оптимизации. этого ключевого слова с точки зрения эффективности.

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

существуетПредвзятые блокировки и упрощенные блокировки включены по умолчанию в JDK 1.6., Предвзятую блокировку можно отключить с помощью -XX:-UseBiasedLocking.

4.1 Спин-блокировки

Для блокировки и пробуждения потоков требуется, чтобы ЦП переключился из пользовательского режима в основной режим., частая блокировка и пробуждение — это большая нагрузка на ЦП, что неизбежно оказывает большое давление на одновременную производительность системы. В то же время мы обнаружили, что во многих приложенияхСостояние блокировки объекта будет длиться только в течение короткого периода времени, и не стоит часто блокировать и пробуждать поток в течение этого короткого периода времени..

Итак, введение спин-блокировки, что такое спин-блокировка?

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

Спин-блокировка подходит для случая, когда критическая область, защищаемая замком, очень мала.Если критическая область мала, время, занимаемое замком, очень короткое.. Ожидание вращения не заменяет блокировку, хотя и позволяет избежать накладных расходов на переключение потоков, но занимает время процессора ЦП.Если поток, удерживающий блокировку, вскоре освобождает блокировку, то эффективность спина очень высока, в противном случае поток спина будет потреблять ресурсы обработки напрасно., он не будет выполнять никакой значимой работы, обычно занимая канаву, не гадя, что приведет к потере производительности. так,Должно быть ограничение на время ожидания спина (количество спинов), если спин превышает определенное время и все еще не получает блокировку, он должен быть приостановлен.

Спин-блокировки появились в JDK 1.4.2 и по умолчанию отключены., но его можно включить с помощью -XX:+UseSpinning,Включено по умолчанию в JDK1.6. Количество одновременных вращений по умолчанию равно 10,Можно настроить параметром -XX:PreBlockSpin.

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

4.2 Адаптивные спин-блокировки

В JDK 1.6 появилась более умная спин-блокировка, адаптивная спин-блокировка.Так называемая самоадаптация означает, что количество вращений больше не фиксировано, оно определяется предыдущим временем вращения на том же замке и состоянием владельца замка.. Так как же он выполняет адаптивное вращение?

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

Благодаря адаптивной спиновой блокировке, постоянному совершенствованию информации о запуске программ и мониторинге производительности предсказание виртуальной машиной состояния блокировки программы будет становиться все более и более точным, а виртуальная машина будет становиться все более и более интеллектуальной.

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

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

Основой для устранения блокировок является поддержка данных анализа побегов.

Если нет конкуренции, зачем блокировка? Таким образом, устранение блокировок экономит время на бессмысленные запросы блокировок. Определяется ли экранирование переменной анализом потока данных для виртуальной машины, но непонятно ли это программисту? Добавляете ли вы синхронизацию перед блоком кода, о котором вы знаете, что нет гонки данных? Но иногда программы не то, что мы думаем? Хотя использование блокировок не показано, при использовании некоторых встроенных API JDK, таких как StringBuffer, Vector, HashTable и т. д., в настоящее время будут выполняться невидимые операции блокировки. Например, метод append() для StringBuffer и метод add() для Vector:

public void vectorTest(){
    Vector<String> vector = new Vector<String>();
    for(int i = 0 ; i < 10 ; i++){
        vector.add(i + "");
    }

    System.out.println(vector);
}

При запуске этого кодаJVM может четко определить, что переменная vector не выходит за пределы метода vectorTest()., поэтому JVM может смело исключить операцию блокировки внутри вектора.

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

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

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

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

Как в приведенном выше примере:

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

4.5 Блокировка смещения

Предвзятая блокировка — важное введение в JDK6, потому что авторы HotSpot в результате исследований и практики обнаружили,В большинстве случаев блокировки не только не являются многопоточными, но и всегда запрашиваются несколько раз одним и тем же потоком., чтобы удешевить получение блокировок потоками, вводятся смещенные блокировки.

Блокировка смещения — это механизм, используемый при выполнении блока кода в одном потоке., если в многопоточной параллельной среде (то есть поток A не закончил выполнение блока синхронизированного кода, а поток B инициировал заявку на блокировку), он обязательно будет преобразован в облегченную блокировку или в тяжеловесную блокировку.

В JDK5 смещенная блокировка по умолчанию отключена, а в JDK6 смещенная блокировка по умолчанию включена.. Если количество параллелизма велико и время выполнения синхронизированного блока кода велико, вероятность одновременного доступа нескольких потоков очень высока.Вы можете использовать параметр -XX:-UseBiasedLocking, чтобы запретить предвзятую блокировку (но это параметр JVM, и его нельзя использовать для индивидуальной установки определенных блокировок объектов).

Основная цель введения смещенных замков заключается в том, чтобы:Чтобы свести к минимуму ненужные облегченные пути выполнения блокировки без конкуренции за многопоточность.. Поскольку операции блокировки и разблокировки облегченных замков должны полагаться на несколько атомарных инструкций CAS,Блокировка Bias требуется только для инструкций Atom CAS при замене ThreintID.(Поскольку смещенная блокировка должна быть отозвана в случае многопоточной конкуренции, затраты производительности на отмену смещенной блокировки также должны быть меньше, чем затраты производительности на сохраненную атомарную инструкцию CAS).

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

Так как же предвзятая блокировка уменьшает количество ненужных операций CAS? Для начала рассмотрим проблему блокировок без конкуренции:

Почти все блокировки теперь являются реентерабельными, т. е. поток, получивший блокировку, может блокировать/разблокировать отслеживаемый объект несколько раз., в соответствии с предыдущим дизайном HotSpot каждая блокировка/разблокировка будет включать некоторые операции CAS (например, операции CAS в очереди ожидания),Операции CAS задерживают местные вызовы, так что идея предвзятой блокировки таковаКак только поток получает объект монитора в первый раз, а затем «смещает» объект монитора к потоку, последующие вызовы могут избежать операций CAS., грубо говоря, это установка переменной, если она окажется истинной, нет необходимости проходить различные процессы блокировки/разблокировки.

Почему CAS вводит локальную задержку? Это начинается с архитектуры SMP (симметричная многопроцессорная система).На следующем рисунке примерно показана структура SMP:

SMP(对称多处理器)架构

это значитВсе ЦП будут совместно использовать системную шину (BUS), которая соединяется с основной памятью по этой шине..Каждое ядро ​​имеет свой кэш первого уровня, и каждое ядро ​​симметрично распределено относительно BUS, поэтому такая структура называется «симметричный мультипроцессор»..

Полное название CAS — Compare-And-Swap, это атомарная инструкция ЦП.Его функция состоит в том, чтобы ЦП атомарно обновлял значение определенной позиции после сравнения.После расследования было обнаружено, чтоЕго реализация основана на инструкциях сборки аппаратной платформы, то есть CAS реализуется аппаратно, а JVM просто инкапсулирует вызов сборки., эти классы AtomicInteger используют эти инкапсулированные интерфейсы.

Например: Core1 и Core2 могут одновременно загружать значение определенного места в основной памяти в свой собственный кэш L1,Когда Core1 изменяет значение этого местоположения в своем собственном кэше L1, он «аннулирует» значение, соответствующее кэшу L1 в Core2, через шину, и как только Core2 обнаруживает, что значение в его кэше L1 недействительно (это называется промахом попадания в кэш). ), последнее значение адреса будет загружено из памяти через шину, а обмен данными по шине туда и обратно называется «трафик согласованности кеша».потому что автобус предназначен для фиксированной «возможности связи»,Если трафик когерентности кеша слишком велик, шина станет узким местом.. И когда значения в Core1 и Core2 снова совпадают, это называется «Консистентность кеша»,С этой точки зрения конечной целью конструкции блокировки является уменьшение трафика согласованности кеша..

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

Консистенция кэша:

Согласованность кэша, упомянутая выше, на самом деле поддерживается протоколом.Теперь общепринятым протоколом является MESI (самый ранний из поддерживаемых Intel).Для конкретной справки:En. Wikipedia.org/wiki/MES i_scared….

Исключения для когерентного трафика кэша:

На самом деле не все CAS вызывают штормы шины, что связано с протоколом когерентности кэша.blogs.Oracle.com/Dave/entry/…

Архитектура NUMA (неоднородная архитектура доступа к памяти):

В соответствии с SMP существует также асимметричная многопроцессорная архитектура, которая в основном используется на некоторых high-end процессорах.Главная особенность в том, что нет шины, нет общей основной памяти, и каждое Ядро имеет свою собственную память.Эта структура тут не обсуждается..

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

  1. Определите, находится ли Mark Word в отклоняемом состоянии, то есть является ли это смещенной блокировкой 1, а флаг блокировки равен 01;
  2. Если он находится в состоянии отклонения, проверьте, является ли идентификатор потока текущим идентификатором потока., если да, то перейти к шагу (5), иначе перейти к шагу (3);
  3. Если идентификатор тестового потока не является идентификатором текущего потока, затем конкурировать за блокировку через операцию CAS, если соревнование прошло успешно, заменить идентификатор потока Mark Word на текущий идентификатор потока, в противном случае выполнить поток (4);
  4. Неспособность конкурировать за блокировку через CAS доказывает, что существует ситуация конкуренции нескольких потоков.Когда достигается глобальная точка безопасности, поток, который получает смещенную блокировку, приостанавливается, а смещенная блокировка обновляется до облегченной блокировки., а затем поток, заблокированный в безопасной точке, продолжает выполнять блок синхронизированного кода;
  5. Выполнить синхронизированный блок кода;

Выпуск смещенного замка принимаетМеханизм, который освобождает блокировки только в результате конкуренции. Потоки не будут активно снимать предвзятые блокировки и должны ждать, пока другие потоки начнут конкурировать.. Необходимость отзыва предвзятых блокировокдождаться глобальной точки сохранения(На данный момент код не выполняется). Шаги следующие:

  1. Приостановить поток, владеющий смещенной блокировкой;
  2. Определите, заблокирован ли объект блокировки, Йена, восстановить до заблокированного состояния (01), чтобы позволить оставшиеся нити конкурировать. Да,Затем повесьте удерживание блокировки для текущей резьбы, и указатель на текущий замок потока записанного адреса в Word Mark The Object Mark, обновляющуюся с легким состоянием блокировки (00), а затем восстановить текущий поток удерживает блокировку в свет в среднем весе Режим блокировки конкуренции;

Примечание: здесь будетПередача блокировки не происходит, когда текущий поток приостанавливается и возобновляется, еще в руках тока нить, просто вперемешку с"Изменить идентификатор потока в заголовке объекта на указатель на адрес записи блокировки"Такая вещь.

偏向锁的获取和释放过程

4.6 Легкие замки

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

  1. Когда поток входит в синхронизированный блок,Если состояние блокировки объекта синхронизации является состоянием отсутствия блокировки (флаг блокировки равен «01», независимо от того, является ли это смещенной блокировкой, равен «0»), виртуальная машина сначала создаст запись о блокировке в кадре стека объекта. текущая нить (запись блокировки) Пространство, используемое для хранения текущей копии объекта блокировки Mark Word., официально именуемый Displaced Mark Word. В это время состояние стека потока и заголовка объекта показано на следующем рисунке:

    轻量级锁CAS操作之前线程堆栈与对象的状态

  2. Скопируйте Mark Word в заголовке объекта в Lock Record;

  3. После успешного копированияВиртуальная машина будет использовать операцию CAS, чтобы попытаться обновить слово блокировки в слове метки объекта до указателя на запись блокировки текущего потока и указать указатель владельца в записи блокировки на слово метки объекта.. Если обновление прошло успешно, перейдите к шагу (4), в противном случае перейдите к шагу (5);

  4. Если действие обновления выполнено успешно, то текущий поток владеет блокировкой объекта, а флаг блокировки объекта Mark Word устанавливается в «00», что означает, что объект находится в состоянии упрощенной блокировки., состояние стека потока и заголовка объекта показано на следующем рисунке:

    轻量级锁CAS操作之后线程堆栈与对象的状态

  5. Если эта операция обновления не удалась, виртуальная машина сначала проверяет, указывает ли слово блокировки в слове метки объекта на фрейм стека текущего потока., если это так, то это означает, что текущий поток уже владеет блокировкой этого объекта, то он может напрямую войти в блок синхронизации для продолжения выполнения.В противном случае это означает, что несколько потоков конкурируют за блокировку и вступают в выполнение цикла (3). флаг станет "10", Mark What, хранящийся в Word, является указателем на тяжеловесную блокировку (мьютекс), а текущий поток и поток, ожидающий блокировки, также перейдут в состояние блокировки.

Разблокировка облегченного замка также осуществляется посредством операции CAS.Основные этапы заключаются в следующем:

  1. Попробуйте заменить текущее слово маркировки объектом Displaced Mark Word, скопированным в потоке с помощью операции CAS.;
  2. Если замена прошла успешноВесь процесс синхронизации завершен и возвращается в состояние без блокировки (01).;
  3. Если замена не удалась,Это означает, что другие потоки пытались получить блокировку (блокировка в это время была раздута), тогда необходимо разбудить приостановленный поток, освобождая блокировку.;

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

轻量级锁的获取和释放过程

  1. Почему слово Mark в заголовке объекта должно быть скопировано в запись блокировки стека потока при обновлении до облегченной блокировки??

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

  2. Почему попытка CAS не удалась и при каких обстоятельствах она не удастся?

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

    Затем поток B раскручивает CAS, ожидая, пока идентификатор блокировки заголовка объекта вернется в состояние без блокировки или содержимое заголовка объекта будет равно HashCode объекта (поскольку это значение потока B перед операцией CAS), это означает, что выполнение потока A завершается (см. отзыв облегченной блокировки позже, заголовок объекта будет сброшен только после того, как поток A завершит отзыв блокировки). наконец удалось, поэтому поток B получил блокировку и разрешение на выполнение кода синхронизации. Если время выполнения потока A велико, а поток B не может выполниться после нескольких тактов CAS, блокировка расширяется до тяжеловесной блокировки, то есть поток B приостанавливается и блокируется, ожидая повторного планирования.

Вот как понимать "легкий"?«Легкий вес» - это традиционный замок по отношению к использованию METEX операционной системы для достижения с точки зрения. Однако, прежде всего, необходимо это подчеркнуть.Облегченные блокировки не используются для замены тяжеловесных замков. Его первоначальная цель состоит в том, чтобы снизить потребление производительности традиционными тяжеловесными блокировками без многопоточной конкуренции..

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

4.7 Тяжелый замок

Синхронизация выполняется внутренним объектом, называемымМонитор блокировки (монитор) для достижения.Но суть блокировки монитора реализуется за счет использования Mutex Lock базовой операционной системы. Операционная система должна переключаться из состояния пользователя в состояние ядра, чтобы переключаться между потоками. Эта стоимость очень высока, а переход между состояниями занимает относительно много времени., поэтому Synchronized неэффективен. Поэтому такой тип блокировки, зависящий от реализации операционной системы, называется Mutex Lock.«Тяжеловесный замок».

4.8 Переключение между тяжелыми замками, облегченными замками и смещенными замками

重量级锁、轻量级锁和偏向锁之间转换

Synchronized偏向锁、轻量级锁及重量级锁转换流程

5 Плюсы и минусы замков

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

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

В третьем случае ввод блока синхронизированного кода простоЧтобы установить смещенные блокировки, отменить смещенные блокировки, установить облегченные блокировки и обновить до тяжеловесных блокировок, мы должны полагаться на тяжеловесные блокировки для решения проблемы в конце, что намного дороже, чем непосредственное использование тяжеловесных блокировок.. Так что какую технологию использовать, зависит от среды и сцены, в которой она находится.В подавляющем большинстве случаев предвзятые блокировки эффективны, основываясь на эмпирическом правиле, согласно которому «большинство блокировок будут одновременно применяться только одним и тем же потоком», установленным авторами HotSpot..

锁的优劣

6 Расширенная информация

  1. Синхронизированная реализация анализа исходного кода JVM
  2. Спин-блокировки, спин-блокировки в очереди, блокировки MCS, блокировки CLH
  3. Глубокое понимание принципа синхронизированной реализации параллелизма Java.