Принцип синхронизации и его применение (подробно и серьезно)

Java
Принцип синхронизации и его применение (подробно и серьезно)

1 Обзор

До jdk1.6 синхронизация основывалась на базовой операционной системе.Mutex LockРеализовано, каждое приобретение и снятие блокировки будет приноситьПереключение между пользовательским режимом и режимом ядра, тем самым увеличиваянакладные расходы на производительность. В случае интенсивной конкуренции замков синхронные замки работают плохо.JDK 1.6, Java делает синхронизированную блокировку синхронизацииполностью оптимизирован, и даже в некоторых сценариях его производительность превзошла блокировку синхронизации Lock.

Давайте сначала объясним основной принцип синхронизированного ключевого слова, а затем объясним его применение.

2. Принцип синхронного выполнения

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

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

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

对象

  • Переменная экземпляра: хранит информацию об атрибутах класса, включая информацию об атрибутах родительского класса.Если часть экземпляра массива также включает длину массива, эта часть памяти выравнивается по 4 байтам.
  • Данные заполнения: Поскольку виртуальная машина требует, чтобы начальный адрес объекта был целым числом, кратным 8 байтам. Данные заполнения не требуются, они нужны только для выравнивания байтов.

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

Объект блокировки, используемый синхронизированным, хранится в заголовке объекта Java.JVM использует 2 ширины слова (одна ширина слова представляет 4 байта, а один байт 8 бит) для хранения заголовка объекта (если объект является массивом, он будет выделять 3 ширина слова, дополнительная ширина в 1 слово записывает длину массива). Его основная структура состоит из Mark Word и Class Metadata Address.

биты виртуальной машины структура объекта инструкция
32/64bit Mark Word Храните хэш-код объекта, информацию о блокировке, возраст поколения или флаг GC и другую информацию.
32/64bit Class Metadata Address Указатель типа указывает на метаданные класса объекта, и JVM использует этот указатель, чтобы определить, экземпляром какого класса является объект.
32/64bit Array length длина массива (если текущий объект является массивом)

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

статус блокировки 25 bit 4 bit Является ли 1 бит смещенной блокировкой 2-битный флаг блокировки
безблокировочное состояние Хэш-код объекта возраст генерации объекта 0 01

Во время работы данные, хранящиеся в Mark Word, изменяются при изменении флага блокировки, и могут существовать следующие четыре типа данных.

Mark Word

2.2 Базовая реализация синхронизированной синхронизации

Как упоминалось выше, JVM реализует синхронизацию методов и синхронизацию блоков кода на основе входа и выхода из объектов Monitor.

Монитор (блокировка монитора) по существу зависит от блокировки Mutex (блокировка взаимного исключения) базовой операционной системы для достижения. Переключение Mutex Lock требует перехода из состояния пользователя в состояние ядра, поэтому переход состояния занимает много процессорного времени. Так что synchronized — это тяжеловесная операция в языке Java.

Ниже поясняется процесс синхронизированного блока кода синхронизации..

public class SynTest{
	public int i;

    public void syncTask(){
		synchronized (this){
			i++;
		}
	}
}

Результат после декомпиляции следующий:

D:\Desktop>javap SynTest.class
Compiled from "SynTest.java"
public class SynTest {
  public int i;
  public SynTest();
  public void syncTask();
}

D:\Desktop>javap -c SynTest.class
Compiled from "SynTest.java"
public class SynTest {
  public int i;

  public SynTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void syncTask();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter
       4: aload_0
       5: dup
       6: getfield      #7                  // Field i:I
       9: iconst_1
      10: iadd
      11: putfield      #7                  // Field i:I
      14: aload_1
      15: monitorexit
      16: goto          24
      19: astore_2
      20: aload_1
      21: monitorexit
      22: aload_2
      23: athrow
      24: return
    Exception table:
       from    to  target type
           4    16    19   any
          19    22    19   any
}

Подписывайтесь на monitorenter и monitorexit:

3: monitorenter
//省略
15: monitorexit
16: goto          24
//省略
21: monitorexit

Из байт-кода видно, что реализация блока синхронизированных операторов использует инструкции monitorenter и monitorexit.

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

public class SynTest{
	public int i;

    public synchronized void syncTask(){
		i++;
	}
}

Декомпилировать:javap -verbose -p SynTest

Classfile /D:/Desktop/SynTest.class
  Last modified 2020年4月2日; size 278 bytes
  SHA-256 checksum 0e7a02cd496bdaaa6865d5c7eb0b9f4bfc08a5922f13a585b5e1f91053bb6572
  Compiled from "SynTest.java"
public class SynTest
  minor version: 0
  major version: 57
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #8                          // SynTest
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Fieldref           #8.#9          // SynTest.i:I
   #8 = Class              #10            // SynTest
   #9 = NameAndType        #11:#12        // i:I
  #10 = Utf8               SynTest
  #11 = Utf8               i
  #12 = Utf8               I
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               syncTask
  #16 = Utf8               SourceFile
  #17 = Utf8               SynTest.java
{
  public int i;
    descriptor: I
    flags: (0x0001) ACC_PUBLIC

  public SynTest();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public synchronized void syncTask();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #7                  // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #7                  // Field i:I
        10: return
      LineNumberTable:
        line 5: 0
        line 6: 10
}
SourceFile: "SynTest.java"

Уведомление:flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED

JVM может определить, является ли метод синхронизированным, по флагу доступа ACC_SYNCHRONIZED в структуре таблицы методов (method_info Structure) в пуле констант методов. Когда метод вызывается, вызывающая инструкция проверяет, установлен ли флаг доступа ACC_SYNCHRONIZED к методу.Если он установлен, исполняемый поток сначала удерживает монитор, затем выполняет метод и, наконец, завершает метод (независимо от того, нормальное завершение или ненормальное завершение) Когда монитор отпускается.

3. Процесс синхронизации (процесс эскалации блокировки)

Как пояснялось выше, синхронизация реализуется блокировкой взаимного исключения операционной системы в начале, что является тяжеловесной операцией.Чтобы снизить потребление производительности, вызванное получением и освобождением блокировок, в JDK 1.6 смещенные блокировки и легкие блокировки были введен блокировка количества. Существует 4 состояния блокировки: состояние без блокировки, состояние смещенной блокировки, облегченное состояние блокировки и тяжелое состояние блокировки. Эти состояния будут постепенно обостряться по мере конкуренции, но не могут быть понижены. Цель состоит в том, чтобы улучшить блокировку и снять блокировку. эффективность.

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

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

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

Процесс получения:

  1. Проверьте, установлен ли флаг блокировки смещения в слове метки доступа на 1 и установлен ли флаг блокировки на 01, что подтверждается как смещаемое состояние.
  2. Если можно отклонить, проверьте, указывает ли идентификатор потока на текущий поток, и если да, выполните синхронный код.
  3. Если он не указывает на текущий поток, используйте CAS для борьбы за блокировку.Если соревнование прошло успешно, установите идентификатор потока в Mark Word на идентификатор текущего потока и сохраните идентификатор текущего потока в записи блокировки в стеке. Рамка.
  4. Если CAS не удается получить предвзятую блокировку, это указывает на конфликт. При достижении глобальной точки безопасности (где в этот момент байт-код не выполняется) поток, удерживающий смещенную блокировку, сначала приостанавливается, а затем проверяется, жив ли поток, удерживающий смещенную блокировку (Поскольку поток, который может удерживать смещенную блокировку, завершил выполнение, но поток не будет активно освобождать смещенную блокировку.).
  5. Если поток не активен, установите заголовок объекта в состояние отсутствия блокировки (бит флага «01»), а затем повторно сместите новый поток; если поток все еще жив, снимите блокировку смещения и выполните обновление до состояние облегченной блокировки (флаг равен "01"), бит равен "00"), в это время облегченная блокировка удерживается потоком, который первоначально удерживал блокировку смещения, и продолжает выполнять свой код синхронизации, в то время как поток, который конкурирует, войдет в спин и будет ждать, чтобы получить облегченную блокировку.

Процесс снятия блокировки:

Фактически, это четыре или пять шагов описанного выше процесса получения блокировки.

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

Отзыв предвзятых блокировок требует ожидания глобальной точки безопасности (в этот момент байт-код не выполняется).

  1. Достигнув глобальной точки сохранения, сначала приостановите поток, владеющий смещенной блокировкой, и проверьте, является ли этот поток или нет.

  2. Неактивный или вышел из блока кода, заголовок объекта устанавливается в состояние без блокировки, а затем перенаправляется на новый поток.

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

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

偏向锁获得流程

существуетИскусство параллельного программирования на JavaВ этой части книги говорится следующее:

Когда поток обращается к синхронизированному блоку и получает блокировку, запись блокировки сохраняет смещение блокировки в заголовке объекта и кадре стека.идентификатор потокаВ будущем нить не требуется выполнять операции CAS для блокировки и разблокировки при входе и выходе в блоке синхронизации и необходимо просто проверить, есть ли предпринимаемый замок, указывающий на текущий резьбу, хранящийся в отметке Заголовок объекта. В случае успеха тема приобрела замок. Если он не удается, вам необходимо проверить, установлен ли флаг предвзятого блокировки в MART WORD (указывает на то, что он в настоящее время является предвзятым замком). Если он не установлен, используйте CAS для конкуренции для блокировки. Если это Установите, попробуйте CAS, чтобы указать предвзятый замок заголовка объекта в текущий поток.

Лично мне кажется, что эта часть книги немного выбилась из колеи, я вижу много блогов, нормальный логический анализ, это тоже надо сначала судитьзамок флаг, чтобы судить о состоянии блокировки, вместо того, чтобы сначала судить, указывает ли идентификатор потока блокировки на себя.

Базовая реализация предвзятых блокировок, если вы хотите узнать о ней больше, вы можете обратиться кЭта статья -- есть объяснение лежащей в основе реализации C++

3.2 Легкий замок

Процесс получения блокировки:

  1. Если объект блокировки не смещен в сторону режима или был смещен в сторону других потоков, в это время будет создано свободное от блокировки состояние.mark wordустановлен вLock Recordв середине, мы называемLock RecordОбъекты хранятся вmark wordПоле называется Displaced Mark Word.
  2. Скопируйте слово Mark в заголовке объекта в запись блокировки. Затем виртуальная машина попытается обновить слово метки объекта до указателя на запись блокировки, используя операцию CAS.
  3. Если обновление прошло успешно, текущий поток получает блокировку и выполняет синхронный код. Если обновление завершается ошибкой, текущий поток пытается получить блокировку с помощью вращения.
  4. Когда вращение превышает определенное количество раз, или один поток удерживает блокировку, один крутится и происходит третье посещение, облегченная блокировка расширяется до тяжелой блокировки.Потоки блокируются.

Процесс снятия блокировки:

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

轻量级锁

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

Процесс блокирования тяжеловесных блокировок описан выше в шаге 4. Облегченные блокировки расширяются до тяжеловесных замков.Бит флага блокировки слова Mark Word обновляется до 10, а слово Mark указывает на мьютекс (тяжеловесную блокировку).

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

4. Три метода применения синхронизированного

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

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

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

4.1 Блокировка объекта

Понимание изображения:

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

В этом случае легко решить некоторые вопросы интервью.

  1. Два синхронизированных метода одного и того же объекта, обращающихся к объекту в двух потоках соответственно

  2. Разные объекты вызывают один и тот же синхронизированный метод в двух потоках

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

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

4.2 Блокировка класса

Существует только один объект класса, под которым можно понимать только одно пространство в любой момент времени с N комнатами, одним замком и одним ключом.

проблема:

  1. Использование класса для вызова двух разных синхронизированных методов непосредственно в двух потоках
  2. Вызов статического или нестатического метода в двух потоках со статическим объектом одного класса
  3. Объект вызывает статический синхронизированный метод и нестатический синхронизированный метод в двух потоках соответственно.

Поскольку блокировка статического объекта фактически блокирует класс (.class), а объект класса только один, можно понять, что в любой момент времени существует только одно пространство, и в нем N комнат и один замок. комнаты (синхронные методы) должны быть взаимоисключающими. Поскольку это вызов объекта, 1 и 2 будут взаимоисключающими.

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

5. Синхронизирована другая оптимизация блокировки

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

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

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

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

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

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

Причины введения спин-блокировок:

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

Спинлок:

Позвольте потоку выполнить бессмысленный цикл занятости (spin) и подождите некоторое время, он не будет немедленно приостановлен (spin не отдает время выполнения процессора), и посмотрите, скоро ли снимет блокировку поток, удерживающий блокировку. Spinlock был представлен в JDK 1.4.2 и отключен по умолчанию, но его можно включить с помощью -XX:+UseSpinning ; он включен по умолчанию в JDK1.6.

Недостатки спин-блокировок:

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

Адаптивная спин-блокировка:

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

6. Реентерабельность синхронизированных

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

7. Резюме и ссылки

резюме

Синхронизированные функции:Гарантирует видимость памяти и атомарность операций. После оптимизации jdk6 производительность synchronized не должна быть хуже, чем у Reentrantlock, реализованного JVM, а иногда даже лучше, по этой же причине многие классы в пакете Java concurrent основаны на синхронизированной реализации .

锁升级过程

использованная литература