Понимание основ замков
Если вы хотите досконально разобраться во всех тонкостях блокировок Java, вам необходимо сначала усвоить следующие базовые знания.
Одна из основ: виды замков
Замки классифицируются макроскопически на пессимистические замки и оптимистичные замки.
оптимистическая блокировка
Оптимистическая блокировка — это оптимистическая идея, то есть она считает, что чтений больше, а записей меньше, а возможность столкнуться с параллельными записями низка. , а затем заблокировать операцию (сравнить с предыдущим номером версии, если совпадает, обновить), при неудаче повторить операции чтение-сравнение-запись.
Оптимистические блокировки в java в основном реализованы через операции CAS.CAS это атомарная операция для обновления.Он сравнивает, совпадает ли текущее значение с входящим значением.Если совпадает,то будет обновлено,иначе не получится.
пессимистический замок
Пессимистическая блокировка — это пессимистическое мышление, то есть она думает, что записей слишком много, а вероятность параллельной записи высока.Каждый раз, когда я иду за получением данных, я думаю, что другие будут их модифицировать, поэтому каждый раз, когда я читаю и пишу данные я заблокирую, чтобы другие думали Чтение и запись этих данных заблокируют до тех пор, пока блокировка не будет получена. Пессимистическая блокировка в java является синхронизированной, а блокировка в рамках AQS заключается в том, чтобы сначала попробовать оптимистическую блокировку cas, чтобы получить блокировку, и если ее невозможно получить, она будет преобразована в пессимистическую блокировку, такую как RetreenLock.
Базовые знания 2: Стоимость блокировки потока Java
Поток Java сопоставляется с собственным потоком операционной системы. Если вы хотите заблокировать или разбудить поток, операционная система должна вмешаться, и вам нужно переключиться между состоянием пользователя и состоянием ядра. Это переключение будет потреблять много системных ресурсов, потому что пользовательский режим и режим ядра имеют свое собственное выделенное пространство памяти, выделенные регистры и т. д. Переключение из пользовательского режима в режим ядра требует передачи в ядро многих переменных и параметров, а ядро также должно защищать некоторые регистровые значения и переменные при переходе из пользовательского режима, чтобы после завершения вызова режима ядра переключиться обратно в пользовательский режим для продолжения работы.
- Если переключение состояния потока является высокочастотной операцией, оно потребляет много процессорного времени;
- Если для простых блоков кода, которые необходимо синхронизировать, операция отложенного получения блокировки занимает больше времени, чем выполнение пользовательского кода, эта стратегия синхронизации, очевидно, очень плоха.
Synchronized приведет к тому, что потоки, которые не могут конкурировать за блокировки, перейдут в состояние блокировки, поэтому это тяжеловесная операция синхронизации в языке java, называемая тяжеловесными блокировками.Чтобы облегчить вышеупомянутые проблемы с производительностью, JVM представила облегченную версию, начиная с версии 1.5. блокировки, спин-блокировки включены по умолчанию, все они являются оптимистичными блокировками.
Выяснение стоимости переключения потоков Java — одна из основ для понимания преимуществ и недостатков различных блокировок в java.
Базовые знания три: markword
Прежде чем представить блокировку Java, давайте поговорим о том, что такое markword. Markword является частью структуры данных объектов Java. Чтобы узнать больше о структуре объектов Java, вы можете нажать здесь. Типы блокировок тесно связаны;
Длина данных маркерного слова составляет 32 бита и 64 бита соответственно в 32-битных и 64-битных виртуальных машинах (без открытия сжатого указателя), а его последние 2 бита — это бит флага состояния блокировки, который используется для обозначения состояния текущего объект и статус объекта. , который определяет содержимое хранилища маркерных слов, как показано в следующей таблице:
государство | бит флага | хранить содержимое |
---|---|---|
не заблокирован | 01 | Хэш-код объекта, возраст генерации объекта |
Легкий замок | 00 | указатель на блокировку записи |
Swell (локаут в тяжелом весе) | 10 | Указатель, выполняющий тяжеловесную блокировку |
маркер ГХ | 11 | Пусто (информация журнала не требуется) |
может быть предвзятым | 01 | Идентификатор потока смещения, отметка времени смещения, возраст создания объекта |
Структура меток 32-битной виртуальной машины в разных состояниях показана на следующем рисунке:
Понимание структуры markword поможет вам понять процесс блокировки и разблокировки блокировки java позже;
резюме
Как упоминалось ранее, в java существует четыре типа блокировок: тяжелые блокировки, спин-блокировки, облегченные блокировки и блокировки со смещением. Различные блокировки имеют разные характеристики.Каждая блокировка может хорошо работать только в своих конкретных сценариях.В Java нет блокировки, которая может иметь отличную эффективность во всех ситуациях.Причина введения такого большого количества блокировок заключается в том,чтобы иметь дело с различными ситуациями;
Как упоминалось ранее, тяжеловесные блокировки относятся к типу пессимистичных замков.Спиновые замки, облегченные замки и предвзятые замки — это оптимистичные замки, так что теперь вы можете примерно понять сферу их применения, но как использовать эти типы замков? анализ их характеристик позже;
заблокировать в java
блокировка спина
Принцип спин-блокировки очень прост: если поток, удерживающий блокировку, может освободить ресурс блокировки за короткое время, то потокам, ожидающим конкурирующую блокировку, не нужно переключаться между режимом ядра и пользовательским режимом, чтобы войти в режим блокировки. состояние ожидания блокировки.Вам нужно только подождать (прокрутить), и блокировка может быть получена сразу после того, как поток, удерживающий блокировку, снимает блокировку, что позволяет избежать потребления переключения между пользовательскими потоками и ядром.
Но вращение нити должно потреблять чашку.Грубо говоря чашка делает бесполезную работу.Если замок не может быть получен все время, то нить не может всегда занимать кружку чашечки для бесполезной работы,поэтому необходимо установить максимальное время ожидания отжима.
Если время выполнения потока, удерживающего блокировку, превышает максимальное время ожидания вращения и блокировка не снимается, другие потоки, претендующие на блокировку, все равно не смогут получить блокировку в течение максимального времени ожидания. конкурирующий поток перестанет вращаться в состояние блокировки.
Преимущества и недостатки спин-блокировок
Спин-блокировки максимально сокращают блокировку потоков, что может значительно повысить производительность для блоков кода, которые не имеют интенсивной конкуренции за блокировки и занимают очень короткое время блокировки, потому что потребление спина будет меньше, чем при блокировке и приостановке потока, а затем пробуждения, операции, вызывающие два переключения контекста для потока!
Однако, если конкуренция за блокировку является жесткой или поток, удерживающий блокировку, должен занимать блокировку в течение длительного времени для выполнения блока синхронизации, использование спин-блокировки не подходит, потому что спин-блокировка всегда занимает ЦП. за бесполезную работу перед получением блокировки. Если XX не XX, и существует большое количество потоков, конкурирующих за блокировку одновременно, получение блокировки займет много времени. Потребление спина потока больше, чем потребление операций блокировки и приостановки потока, а другие потоки, которым требуется ЦП, не могут получить ЦП, что приводит к трате ЦП. Так что в этом случае мы должны закрыть спин-блокировку;
Порог времени спин-блокировки
Назначение спин-блокировки — не освобождать ресурсы, занимающие ЦП, и обрабатывать их сразу после получения блокировки. Но как выбрать время выполнения спина? Если время выполнения вращения слишком велико, большое количество потоков будет находиться в состоянии вращения и занимать ресурсы ЦП, что повлияет на производительность всей системы. Поэтому выбор периода вращения имеет особое значение!
Для выбора периода отжима JVM в определенной степени прописан предел jdk1.5.В 1.6 введена адаптивная блокировка спина.Адаптивная блокировка спина означает, что время спина уже не фиксируется, а определяется определяется предыдущим временем спина на той же блокировке и состоянием владельца блокировки.В основном считается, что время переключения контекста потока является лучшим временем.В то же время JVM также делает больше под текущую загрузку процессора.
- Вращается все время, если средняя загрузка меньше, чем процессоры
- Если более чем (ЦП/2) потоков вращается, то поток блокируется непосредственно позже
- Если крутящийся поток обнаружит, что Владелец изменился, задержите время вращения (счетчик вращений) или войдите в блок
- Остановить вращение, если процессор находится в режиме энергосбережения
- Наихудшим случаем времени вращения является задержка хранения ЦП (ЦП А хранит часть данных и разница во времени между ЦП Б, знающим данные напрямую)
- Различия между приоритетами потоков должным образом отбрасываются при вращении
Включите блокировку вращения
-XX:+UseSpinning включен в JDK1.6; -XX:PreBlockSpin=10 — количество спинов; После JDK1.7 удалите этот параметр и управляйте им с помощью jvm;
Синхронизированный тяжелый замок
Роль синхронизированного
До JDK1.5 для обеспечения синхронизации использовалось ключевое слово synchronized, и считается, что роль Synchronized всем знакома;
Он может рассматривать любой объект, отличный от NULL, как блокировку.
- При воздействии на метод блокировка является экземпляром объекта (this);
- При использовании статических методов экземпляр класса блокируется, а поскольку связанные данные класса хранятся в постоянной полосе PermGen (jdk1.8 — это метапространство), постоянная полоса используется глобально, поэтому блокировка статического метода эквивалентна класс — глобальная блокировка, которая блокирует все потоки, вызывающие этот метод;
- Когда синхронизация действует на экземпляр объекта, она блокирует все блоки кода, которые используют объект в качестве блокировки. Реализация синхронизированного
Реализация показана на следующем рисунке:
Он имеет несколько очередей, и когда несколько потоков одновременно обращаются к объектному монитору, объектный монитор будет хранить эти потоки в разных контейнерах.
- Конкурсный список: конкурсная очередь, все потоки, запрашивающие блокировки, первыми помещаются в эту конкурсную очередь;
- Список записей: те потоки в списке конфликтов, которые имеют право быть кандидатами в ресурсы, перемещаются в список записей;
- Wait Set: сюда помещаются потоки, вызывающие метод ожидания, которые заблокированы;
- OnDeck: в любое время за ресурсы блокировки конкурирует не более одного потока, и этот поток называется OnDeck;
- Владелец: Поток, который в данный момент получил ресурс, называется Владелец;
- !Owner: Поток, который в данный момент снимает блокировку.
JVM берет часть данных из хвоста очереди для кандидата на конкуренцию блокировок (OnDeck) каждый раз, но в случае параллелизма к ContentionList будет обращаться большое количество параллельных потоков для CAS. Конкуренция за хвостовой элемент, JVM переместит некоторые потоки в EntryList в качестве конкурирующего потока-кандидата. Поток-владелец перенесет некоторые потоки из ContentionList в EntryList, когда он будет разблокирован, и назначит поток в EntryList потоком OnDeck (обычно поток, который идет первым). Поток-владелец не передает блокировку напрямую потоку OnDeck, а передает право конкуренции за блокировку OnDeck, и OnDeck должен снова конкурировать за блокировку. Хотя это и приносит в жертву некоторую справедливость, это может значительно повысить пропускную способность системы.В JVM такое поведение выбора также называется "конкурентным переключением".
После того, как поток OnDeck получит ресурс блокировки, он станет потоком Owner, а поток, не получивший ресурс блокировки, останется в EntryList. Если поток Owner заблокирован методом ожидания, он будет передан в очередь WaitSet до тех пор, пока не будет разбужен с помощью notify или notifyAll в определенное время, и повторно войдет в EntryList.
Все потоки в ContentionList, EntryList и WaitSet заблокированы, и блокировка осуществляется операционной системой (реализованной функцией ядра pthread_mutex_lock в ядре Linux).
Synchronized — это несправедливая блокировка.Синхронизированный Когда поток входит в ContentionList, ожидающий поток сначала попытается получить блокировку с помощью спина. Если он не может ее получить, он войдет в ContentionList, что явно несправедливо по отношению к потоку, который вошел в очередь. Дело в получении спина Поток блокировки может также напрямую вытеснять ресурс блокировки потока OnDeck.
Блокировка смещения
Java Biased Locking — это многопоточная оптимизация, представленная в Java 6. Предвзятая блокировка, как следует из названия, будет смещена в сторону первого потока, который обращается к блокировке.Если к блокировке синхронизации обращается только один поток во время работы и нет многопоточного конфликта, потоку не нужно запускать синхронизация, в этом случае к потоку будет добавлена смещенная блокировка. Если другие потоки вытесняют блокировку во время работы, поток, удерживающий смещенную блокировку, будет приостановлен, и JVM удалит с него смещенную блокировку и восстановит стандартную облегченную блокировку.
Это еще больше повышает производительность программы, устраняя примитивы синхронизации при отсутствии конкуренции за ресурсы.
Реализация предвзятой блокировки
Процесс получения предвзятой блокировки:
- Независимо от того, установлен ли флаг смещенной блокировки в слове метки доступа в 1 и равен ли флаг блокировки 01, это подтверждается как смещаемое состояние.
- Если он находится в состоянии отклонения, проверьте, указывает ли идентификатор потока на текущий поток, если да, перейдите к шагу 5, в противном случае перейдите к шагу 3.
- Если идентификатор потока не указывает на текущий поток, блокировка оспаривается посредством операции CAS. Если соревнование завершилось успешно, установите идентификатор потока в слове метки на текущий идентификатор потока, а затем выполните 5; если соревнование не удалось, выполните 4.
- Если CAS не удается получить предвзятую блокировку, это указывает на конфликт. Когда глобальная точка безопасности (safepoint) достигнута, поток, который получает смещенную блокировку, приостанавливается, смещенная блокировка обновляется до облегченной блокировки, а затем поток, заблокированный в точке безопасности, продолжает выполнять код синхронизации. (Снятие блокировки смещения приведет к остановке слова)
- Выполнить синхронный код.
Предвзятое снятие блокировки:
Отзыв предвзятой блокировки упоминается в четвертом шаге выше. Смещенная блокировка снимет блокировку только тогда, когда другие потоки попытаются конкурировать за смещенную блокировку, и поток не будет проявлять инициативу, чтобы снять смещенную блокировку. Отзыв предвзятой блокировки должен дождаться глобальной точки безопасности (в этот момент времени байт-код не выполняется), он сначала приостановит поток, которому принадлежит предвзятая блокировка, определит, находится ли объект блокировки в заблокированном состоянии, и вернуться в разблокированное состояние после отмены блокировки со смещением.Статус блокировки (флаг "01") или упрощенной блокировки (флаг "00").
Применимые сценарии для смещенных замков
Всегда есть только один поток, выполняющий синхронизированный блок. Прежде чем он завершит выполнение и освободит блокировку, ни один другой поток не выполнит синхронизированный блок. Он используется, когда нет конкуренции блокировок. Когда конкуренция есть, он будет обновлен до облегченного замок, а затем обновлен до легкого замка.Когда используется блокировка величины, блокировка смещения должна быть отменена, и при снятии блокировки смещения будет вызвана операция остановки слова; Когда есть конкуренция блокировок, блокировка смещения будет выполнять много дополнительных операций, особенно когда смещение будет отменено, это приведет к входу в безопасную точку, а безопасная точка вызовет stw, что приведет к снижению производительности. , его следует отключить;
Просмотр киосков — журналы киосков Safepoint
Чтобы просмотреть остановку безопасной точки, вы можете открыть журнал безопасных точек, установив параметр JVM -XX:+PrintGCApplicationStoppedTime, чтобы распечатать время остановки системы, добавив -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 Эти два параметра будут печатать из подробной информации, вы можете увидеть паузы, вызванные использованием смещенных замков.Время очень короткое, но в случае серьезных разногласий количество пауз также будет очень большим;
Примечание. Журнал точек сохранения нельзя включать постоянно:
- Журнал точки безопасности выводится на стандартный вывод по умолчанию.Одним из них является чистота журнала стандартного вывода, а другим является то, что файл, перенаправленный стандартным выводом, может быть заблокирован, если он не находится в /dev/shm.
- Для некоторых очень коротких пауз, таких как отмена блокировки смещения, стоимость печати превышает стоимость самой паузы.
- Журнал точки безопасности печатается внутри точки безопасности, что само по себе увеличивает время паузы точки безопасности.
Поэтому журнал безопасности следует включать только для устранения неполадок. Если вы хотите открыть его в производственной системе, добавьте следующие четыре параметра: -XX:+UnlockDiagnosticVMOptions -XX: -DisplayVMOutput -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm.log Включите диагностику (просто откройте дополнительные параметры флага и не активируйте определенный флаг), отключите вывод журналов ВМ на стандартный вывод и вывод в отдельный файл, каталог /dev/shm (файловая система памяти).
Этот журнал разделен на три части: Первая часть — это временная метка, тип операции виртуальной машины. Вторая часть представляет собой обзор темы, заключенный в квадратные скобки. total: общее количество потоков в safepoint Initial_running: количество потоков в состоянии выполнения в начале точки сохранения. wait_to_block: количество потоков, ожидающих приостановки работы виртуальной машины перед запуском.
Третья часть — это различные этапы достижения безопасной точки и время выполнения операции, самым важным из которых является vmop.
- spin: время ожидания ответа потока на вызов точки сохранения;
- блок: время, необходимое для приостановки всех потоков;
- синхронизация: равна вращению + блоку, что представляет собой время от начала до входа в безопасную точку, которое можно использовать для оценки времени, необходимого для входа в безопасную точку;
- уборка: время, потраченное на уборку;
- vmop: фактическое время выполнения операции VM.
Можно видеть, что все эти многочисленные, но короткие точки безопасности являются RevokeBias, и приложения с высокой степенью параллелизма отключат предвзятые блокировки.
JVM вкл/выкл блокировку смещения
Включить предвзятую блокировку: -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 Отключить предвзятую блокировку: -XX:-UseBiasedLocking
Легкий замок
Облегченные блокировки обновляются по смещению.Когда поток входит в синхронизированный блок, смещенные блокировки обновляются до облегченных замков, когда второй поток присоединяется к состязанию за блокировку.
Процесс запирания облегченного замка:
-
Когда код входит в блок синхронизации, если состояние блокировки объекта синхронизации является свободным от блокировки (флаг блокировки равен «01», независимо от того, является ли это смещенной блокировкой, равен «0»), виртуальная машина сначала создаст кадр стека в текущем потоке Пространство с именем Lock Record используется для хранения копии текущего слова метки объекта блокировки, официально называемого смещенным словом метки. В это время состояние стека потока и заголовка объекта показано на рисунке:
показано.
-
Скопируйте слово Mark в заголовке объекта в запись блокировки;
-
После успешного копирования виртуальная машина будет использовать операцию CAS, чтобы попытаться обновить слово метки объекта до указателя на запись блокировки и указать указатель владельца в записи блокировки на слово метки объекта. Если обновление прошло успешно, перейдите к шагу 4, в противном случае перейдите к шагу 5.
-
Если действие обновления выполнено успешно, то поток владеет блокировкой объекта, а флаг блокировки объекта Mark Word устанавливается в «00», что означает, что объект находится в состоянии облегченной блокировки. стек потоков и состояние заголовка объекта, как показано на рисунке.
-
Если операция обновления не удалась, виртуальная машина сначала проверяет, указывает ли Mark Word объекта на фрейм стека текущего потока.Если это так, это означает, что текущий поток уже владеет блокировкой объекта и может напрямую введите блок синхронизации, чтобы продолжить выполнение. В противном случае это означает, что несколько потоков конкурируют за блокировку, и текущий поток пытается использовать вращение для получения блокировки.Если вращение прошло успешно, он все еще находится в облегченном состоянии.
-
Если прокрутка завершится неудачно, облегченная блокировка превратится в тяжеловесную, значение состояния флага блокировки станет равным «10», а указатель на тяжеловесную блокировку (мьютекс) будет сохранен в слове метки, а блокировка будет ожидание блокировки позже.Поток также входит в состояние блокировки. Текущий поток пытается использовать spin для получения блокировки Spin — это процесс получения блокировки в цикле, чтобы не блокировать поток.
Легкая разблокировка замка
Перспектива потока блокировки освобождения:
Переключение с облегченной блокировки на весовую происходит, когда облегченная блокировка освобождает блокировку.При получении блокировки она копирует метку заголовка объекта блокировки.При освобождении блокировки, если она обнаружила, что удерживала блокировку во время период Есть другие потоки, пытающиеся получить блокировку, и поток изменил маркерное слово. Если они несовместимы, переключитесь на весовую блокировку.
Поскольку тяжеловесная блокировка была изменена, все отображаемые слова меток не совпадают с исходными словами меток.
Чтобы исправить это, нужно сравнить состояние метки obj перед входом в мьютекс. Подтвердите, удерживается ли маркерное слово другими потоками.
В это время, если поток освободил маркерное слово, он может напрямую войти в поток после прохождения CAS, не входя во мьютекс, что и является эффектом.
Попробуйте получить перспективу потока блокировки:
Если поток пытается получить блокировку, а облегченная блокировка занята другими потоками, он изменит метку и изменит тяжеловесную блокировку, указывая на то, что пришло время ввести весовую блокировку.
Есть еще одно замечание: поток, ожидающий облегченной блокировки, не будет блокироваться, он будет продолжать вращаться в ожидании блокировки и модифицировать markword, как указано выше.
Это спин-блокировка.Поток, который пытается получить блокировку, не будет приостановлен, если блокировка не получена, а вместо этого выполнит пустой цикл, то есть спин. После нескольких вращений, если замок не был получен, он приостанавливается, и код выполняется, когда замок получен.
Суммировать
Процесс выполнения синхронизирован:
- Проверить, есть ли ID текущего потока в Mark Word, если да, то это означает, что текущий поток находится в смещенной блокировке.
- Если нет, используйте CAS, чтобы заменить Mard Word идентификатором текущего потока.В случае успеха это означает, что текущий поток получает блокировку смещения, а флаг смещения устанавливается в 1.
- Если это не удается, проводится соревнование, предвзятая блокировка отменяется, а затем модернизируется до облегченной блокировки.
- Текущий поток использует CAS для замены Mark Word в заголовке объекта указателем записи блокировки.В случае успеха текущий поток получает блокировку
- Если это не удается, указывая на то, что другие потоки конкурируют за блокировку, текущий поток пытается использовать вращение, чтобы получить блокировку.
- Если вращение прошло успешно, оно все еще находится в облегченном состоянии.
- Если вращение не удается, переходите к тяжелому замку.
Вышеупомянутые блокировки реализованы внутри JVM.Когда мы выполняем синхронизированный блок, JVM решает, как выполнить операцию синхронизации в соответствии с включенными блокировками и конкуренцией текущего потока;
Когда все блокировки включены, при входе в критическую секцию поток сначала получит смещенную блокировку.Если смещенная блокировка уже есть, он попытается получить облегченную блокировку, включить блокировку вращения, lock , используется тяжеловесная блокировка, а поток, который не получил блокировку, блокируется и приостанавливается до тех пор, пока поток, удерживающий блокировку, не выполнит блок синхронизации, чтобы разбудить их;
Смещенная блокировка используется в случае отсутствия конкуренции за блокировку, то есть синхронизация открывается до выполнения текущего потока, и ни один другой поток не будет выполнять блок синхронизации. быть выполнен.Обновить до облегченной блокировки.Если облегченная блокировка не получает блокировку после того, как вращение достигает порога, она будет повышена до тяжелой блокировки;
Предвзятую блокировку следует отключить, если конкуренция за поток высока.
оптимизация блокировки
Описанные выше блокировки не поддаются контролю в нашем коде, но мы можем оптимизировать блокировку наших собственных потоков, изучая приведенные выше идеи;
Сокращение времени блокировки
Коды, которые не нужно выполнять синхронно, не следует размещать в синхронном фасте, если они могут быть выполнены в синхронном фасте, чтобы блокировку можно было снять как можно быстрее;
Уменьшить детализацию блокировки
Его идея состоит в том, чтобы разделить физическую блокировку на несколько логических блокировок, чтобы повысить степень параллелизма и тем самым уменьшить конкуренцию блокировок. Его мысль также состоит в том, чтобы заменить пространство временем;
Многие структуры данных в Java используют этот метод для повышения эффективности параллельных операций:
ConcurrentHashMap
ConcurrentHashMap в java до того, как jdk1.8 использует массив сегментов
Segment< K,V >[] segments
Сегмент наследуется от ReenTrantLock, поэтому каждый сегмент представляет собой блокировку с повторным входом. Каждый сегмент имеет массив HashEntry
LongAdder
Идея реализации LongAdder также похожа на ConcurrentHashMap.LongAdder имеет массив ячеек, который динамически изменяется в соответствии с текущей ситуацией параллелизма, и в объекте Cell имеется длинное значение для хранения значения; Если в начале нет одновременной конкуренции или когда массив ячеек инициализируется, cas будет использоваться для накопления значения в базе переменной-члена.В случае параллельной конкуренции LongAdder инициализирует массив ячеек и выберет его. в массиве ячеек Когда ячейка заблокирована, количество ячеек в массиве может быть изменено одновременно многими потоками Наконец, значение в каждой ячейке в массиве добавляется, а значение базы добавляется , что является окончательным значением; массив ячеек также может быть изменен в соответствии с Текущая ситуация конфликта потоков расширяется. Начальная длина равна 2, и каждое расширение будет удваиваться, пока расширение не станет больше или равно количеству процессоров и больше не расширяются. Вот почему LongAdder более эффективен, чем cas и AtomicInteger. Причина, по которой последние два Все они реализованы volatile + cas, их измерение конкуренции равно 1, а измерение соревнования LongAdder - «Количество ячеек + 1" Почему должно быть +1? Поскольку у него также есть база, при отсутствии конкуренции замков он попытается добавить значение к базе;
LinkedBlockingQueue
LinkedBlockingQueue также воплощает эту идею: вход в очередь в начале очереди, удаление из очереди в конце, использование разных блокировок для постановки в очередь и удаления из очереди более эффективно, чем LinkedBlockingArray только с одной блокировкой;
Детализация разблокировки не может быть неограниченной, и не более одного замка можно разделить на текущее количество замков чашки;
блокировка огрубления
В большинстве случаев мы хотим свести к минимуму степень детализации блокировки, а огрубление блокировки состоит в увеличении детализации блокировки; Детализация блокировок должна быть увеличена в следующих сценариях: Если есть цикл, операции в цикле должны быть заблокированы, мы должны поставить блокировку вне цикла, иначе каждый раз, когда мы входим и выходим из цикла, мы будем входить и выходить из критической секции один раз, и эффективность очень бедных;
Используйте блокировки чтения-записи
ReentrantReadWriteLockЭто блокировка чтения-записи, операция чтения плюс блокировка чтения, можно читать одновременно, операция записи использует блокировку записи, только однопоточная запись;
разделение чтения-записи
КопиОнВритеАррайлист, КопиОнВритеАррайсетКонтейнер CopyOnWrite — это контейнер копирования при записи. Популярное понимание состоит в том, что когда мы добавляем элементы в контейнер, мы не добавляем напрямую в текущий контейнер, а сначала копируем текущий контейнер, копируем новый контейнер, а затем добавляем элементы в новый контейнер. ссылка исходного контейнера на новый контейнер. Преимущество этого в том, что мы можем одновременно читать контейнер CopyOnWrite без блокировки, потому что текущий контейнер не будет добавлять никаких элементов. Поэтому контейнер CopyOnWrite — это еще и некое разделение чтения и записи, чтение и запись разных контейнеров. Параллельный контейнер CopyOnWrite используется для параллельных сценариев с большим количеством операций чтения и меньшим количеством операций записи, поскольку при чтении блокировка отсутствует, но она будет заблокирована при ее изменении, в противном случае это приведет к тому, что несколько потоков будут одновременно копировать несколько копий и изменять их. их собственные;
использовать Кас
Если операция, которую необходимо синхронизировать, выполняется очень быстро, а конкуренция между потоками невелика, эффективнее будет использовать cas, так как блокировка вызовет переключение контекста потока, если переключение контекста занимает много времени. занимает больше времени, чем сама операция синхронизации. И конкуренция потоков за ресурсы не является жесткой, использование операции volatiled+cas будет очень эффективным выбором;
Устранить ложное совместное использование строк кэша
В дополнение к блокировкам синхронизации, которые мы используем в нашем коде, и собственным встроенным блокировкам синхронизации jvm, существует скрытая блокировка, которая представляет собой строки кэша, которые также известны как убийцы производительности. В многоядерном ЦП каждый ЦП имеет свой собственный эксклюзивный кэш L1, кэш L2 и даже общий кэш L3.Для повышения производительности ЦП считывает и записывает данные в наименьшей единице поведения кэша.Да, 32 -битная строка кэша процессора имеет размер 32 байта, а 64-битная строка кэша процессора составляет 64 байта, что вызывает некоторые проблемы. Например, несколько переменных, которые не нуждаются в синхронизации, хранятся в смежных байтах 32 или байтах 64. Когда одна из переменных необходима, они загружаются вместе как строка кэша в частный кэш cup-1 (хотя требуется только одна переменная). , чтение процессора будет использовать наименьшую единицу поведения кэша и считывать соседние переменные вместе), переменная, считываемая в кэш процессора, эквивалентна копии переменной основной памяти, которая также эквивалентна замаскированной форме. добавляется к нескольким переменным в одной и той же строке кэша.Если какая-либо переменная в этой строке кэша изменяется, когда cup-2 должен прочитать эту строку кэша, он должен сначала прочитать всю строку кэша, которая изменилась, обновляется обратно в основную память (даже если другие переменные не изменились), и тогда cup-2 сможет его прочитать, и cup-2, возможно, потребуется изменить переменную строки кеша, чтобы она соответствовала переменной в строке кеша, которую изменил cpu-1. Переменные разные, поэтому это эквивалентно добавлению блокировки синхронизации к нескольким несвязанным переменным; Чтобы предотвратить ложный обмен, разные версии jdk реализованы по-разному:
- До jdk1.7 группа переменных длинного типа будет добавляться до и после переменных, которым нужны эксклюзивные строки кэша, и переменная будет иметь эксклюзивную строку кэша сама по себе, заполняя эти бессмысленные массивы;
- В jdk1.7, поскольку jvm будет оптимизировать эти неиспользуемые переменные, это реализовано путем наследования класса, который объявляет много длинных переменных;
- В jdk1.8 эта проблема решается добавлением аннотации sun.misc.Contended, чтобы аннотация работала, необходимо добавить в jvm следующие параметры: -XX:-RestrictContended
Аннотация sun.misc.Contended добавит 128-байтовый отступ перед переменной, чтобы изолировать текущую переменную от других переменных; В Интернете есть много объяснений о том, что такое строка кэша и как jdk избегает строки кэша, поэтому я не буду подробно объяснять это здесь;