Статья для понимания механизма блокировки Java

Java
Статья для понимания механизма блокировки Java

жизненный опыт

конвейер инструкций

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

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

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

многоуровневый кэш процессора

В компьютерной системе кэш ЦП (CPU Cache, сокращенно кэш) — это компонент, используемый для сокращения среднего времени, необходимого процессору для доступа к памяти. Это второй нисходящий уровень в пирамиде памяти, уступающий только регистрам ЦП. Его емкость намного меньше памяти, но скорость может быть близка к частоте процессора.

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

Причина, по которой кеш эффективен, заключается главным образом в том, что доступ к памяти во время работы программы характеризуется локальностью. Эта локальность включает в себя как пространственную локальность (Spatial Locality), так и временную локальность (Temporal Locality). Используя преимущество этой локализации, кеши могут достигать чрезвычайно высоких показателей попадания.

введение проблемы

атомарность

атомарность: Процесс, в котором выполняются и выполняются все операции или несколько операций, не может быть прерван каким-либо фактором или не выполняться.

Пример метода: {i++ (i — переменная экземпляра)}

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

  • прочитать значение переменной i
  • добавить одну
  • присвоить новое значение переменной i

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

Кроме того, если рассматривать взаимодействие рабочей памяти и основной памяти, то его можно подразделить на следующие операции:

  • read читает из основной памяти в рабочую память (опционально)
  • загружать копии переменных, назначенных рабочей памяти (необязательно)
  • use Значение переменной рабочей памяти передается механизму выполнения.
  • Исполнительный механизм выполняет операцию приращения
  • assign присваивает значение, полученное от механизма выполнения, переменной в рабочей памяти
  • сохранить значение переменной передать рабочую память в основную память (необязательно)
  • write Записывает значение переменной в рабочей памяти в переменную в основной памяти (необязательно)

видимость

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

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

while (flag) {//语句1
   doSomething();//语句2
}

flag = false;//语句3

Поток 1 оценивает флаг флага и выполняет оператор 2, если условия выполнены; флаг флага потока 2 установлен в значение false, но из-за проблемы видимости поток 1 не может его воспринять и всегда будет обрабатывать оператор 2 в цикле.

последовательный

последовательный: То есть порядок выполнения программы выполняется в порядке выполнения кода.

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

if (inited == false) {	
   context = loadContext();   //语句1
   inited = true;             //语句2
}
doSomethingwithconfig(context); //语句3

Поскольку оператор 1 и оператор 2 не имеют зависимостей, оператор 1 и оператор 2 могут выполняться параллельно или оператор 2 может выполняться до оператора 1. Если два потока этого кода выполняются одновременно, поток 1 выполняет оператор 2, но оператор 1 не был выполнен.В это время поток 2 оценивает, что inited является истинным, и выполняет оператор 3, но поскольку контекст не инициализирован, произойдет неизвестное исключение.

Модель памяти JMM

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

раздел памяти

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

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

  • В основной памяти хранятся значения всех общих переменных.
  • Рабочая память (Working Memory) хранит копию значения общей переменной, используемой потоком, в основной памяти.

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

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

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

Правила межпамятного взаимодействия

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

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

Определить правила использования атомарных операций

  1. Потоку не разрешено синхронизировать данные из рабочей памяти в основную память без причины (операция присваивания не выполнялась)
  2. Новую переменную можно создать только в основной памяти, нельзя использовать неинициализированные (загрузить или присвоить) переменную непосредственно в рабочей памяти. То есть, прежде чем реализовывать операции использования и сохранения над переменной, операции присваивания и загрузки должны выполняться самостоятельно.
  3. Переменная может быть заблокирована только одним потоком одновременно, но операция блокировки может многократно выполняться одним и тем же потоком много раз.После многократного выполнения блокировки переменная будет разблокирована только после такого же количества разблокировок. операции выполняются. блокировка и разблокировка должны быть сопряжены.
  4. Если операция блокировки выполняется над переменной, значение переменной в рабочей памяти будет очищено.Прежде чем исполнительный механизм использует переменную, необходимо повторно выполнить операцию загрузки или назначения, чтобы инициализировать значение переменной.
  5. Если переменная ранее не была заблокирована операцией блокировки, ей не разрешается выполнять операцию разблокировки; также не разрешается разблокировать переменную, заблокированную другими потоками.
  6. Перед выполнением операции разблокировки переменной переменная должна быть синхронизирована с оперативной памятью (выполняются операции сохранения и записи)

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

  • Читайте, нагрузка и использование должны появляться в парах, но они не обязаны появляться последовательно. Назначить, хранить, писать одинаково;
  • Рождение и инициализация переменных: переменные могут быть «рождены» только из основной памяти и должны быть инициализированы перед тем, как их можно будет использовать, то есть загрузка/назначение должны выполняться перед использованием/сохранением;
  • После блокировки переменной значение переменной в рабочей памяти будет очищено, и ее необходимо инициализировать перед использованием, перед разблокировкой переменная должна быть синхронизирована обратно в основную память;
  • Переменная может быть заблокирована только одним потоком одновременно, и она должна быть разблокирована несколько раз, когда она заблокирована; незаблокированная переменная не может быть разблокирована, и поток не может разблокировать переменные, заблокированные другими потоками.

Специальные правила для переменных типа long и double

Модель памяти Java требует, чтобы вышеупомянутые 8 операций были атомарными, но для 64-битных типов данных long и double в модели специально определено свободное положение: виртуальной машине разрешено читать и записывать 64-битные данные, которые не модифицируется volatile.Разделите его на две 32-битные операции. То есть, когда он не изменяется с помощью volatile, чтение, прочитанное потоком, не является атомарной операцией, и он может только прочитать значение «полупеременной». Тем не менее, коммерческие виртуальные машины почти всегда реализуют чтение и запись 64-битных данных как атомарные операции, поэтому мы можем игнорировать эту проблему.

происходит до принципа

Модель памяти Java имеет некоторую присущую «упорядоченность», которая не требует какой-либо синхронизации с помощью (изменчивой, синхронизированной и т. д.), сможет обеспечить упорядоченный характер, это часто называют принципом «происходит до».

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

  1. Правило порядка выполнения программы: в потоке операции, написанные логически впереди, выполняются до операций, написанных позже.
  2. Правило блокировки (правило блокировки монитора): сначала выполняется операция разблокировки перед последующей операцией блокировки для той же блокировки. «Следование» относится к хронологическому порядку.
  3. правила изменяемой переменной(Правило энергозависимой переменной): Операция записи в энергозависимую переменную выполняется перед последующей операцией чтения этой переменной. «Следование» относится к хронологическому порядку.
  4. Транзитивность: если операция A встречается в операции B, операция B возникает в операции C, и операция A может быть нарисована первой в операции C.
  5. Правило запуска потока: метод start() объекта Thread предшествует каждому действию этого потока.
  6. Правило прерывания потока: вызов метода прерывания() потока происходит первым, когда код прерванного потока обнаруживает возникновение события прерывания (обнаруженного функцией Thread.interrupted()).
  7. Правило завершения потока: все операции в потоке выполняются первыми при обнаружении завершения потока.Мы можем определить, что поток завершил выполнение, через конец метода Thread.join() и возвращаемое значение Thread.isAlive().
  8. Правило финализации объекта (правило финализатора): инициализация объекта (конец выполнения конструктора) происходит сначала в начале его метода finalize().

проблема решена

атомарность

  • Операции с атомарными переменными, прямо гарантированные JMM, включают чтение, загрузку, использование, назначение, сохранение и запись;
  • Основные типы данных Чтение-запись (рабочая память) является атомной

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

видимость

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

synchronized/LockГарантируется блокировкой и разблокировкой правил использования

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

finalКак только декорированное поле инициализируется в конструкторе, и конструктор не передает ссылку на «это», другие потоки могут сразу увидеть значение конечного поля.

последовательный

volatileОтключить переупорядочивание инструкций

synchronized/Lock"Только один поток может одновременно выполнять операцию блокировки переменной"

Разработка

volatile

Волатильные переменные могут быть изменены, чтобы обеспечить упорядоченность и видимость

последовательный

  • Запись в изменчивую переменную происходит перед последующим чтением переменной. «Следующие» относятся к хронологическому порядку

видимость

  • При записи изменчивой переменной JMM сбрасывает общую переменную из рабочей памяти, соответствующей потоку, в основную память.
  • При чтении изменчивой переменной JMM сделает недействительной рабочую память, соответствующую потоку, и поток затем будет считывать общую переменную из основной памяти.

Volatile очень легкий по сравнению с synchronized/Lock, но сценарии использования ограничены:

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

Принцип реализации

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

Барьеры памяти служат двум целям:

  • Предотвращение переупорядочивания инструкций по обе стороны барьера
  • Принудительная запись грязных данных в буфере/кэше записи обратно в основную память, что делает недействительными соответствующие данные в кеше

final

Для семантики памяти полей final компилятор и процессор должны подчиняться двум правилам переупорядочения (внутренняя реализация также использует барьеры памяти):

  • Правила переупорядочивания для записи конечных полей: нет никакого переупорядочения между записью конечного поля в конструкторе и последующим присвоением ссылки на сконструированный объект ссылочной переменной.
  • Правила переупорядочения чтения конечных полей: первое чтение ссылки на объект, содержащий конечное поле, и последующее начальное чтение конечного поля не могут быть переупорядочены.
public class FinalExample {
       int i;//普通域
       final int j;//final域
       static FinalExample obj;
       
       public FinalExample () {
              i = 1;//写普通域。对普通域的写操作【可能会】被重排序到构造函数之外 
              j = 2;//写final域。对final域的写操作【不会】被重排序到构造函数之外
       }
       
       // 写线程A执行
       public static void writer () {    
              obj = new FinalExample ();
       }
       
       // 读线程B执行
       public static void reader () {    
              FinalExample object = obj;//读对象引用
              int a = object.i;//读普通域。可能会看到结果为0(由于i=1可能被重排序到构造函数外,此时y还没有被初始化)
              int b = object.j;//读final域。保证能够看到结果为2
       }
}

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

Для полей final, которые являются ссылочными типами, правила переупорядочивания для записи полей final накладывают следующие ограничения на компилятор и процессор:

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

synchronized

Synchronized используется для изменения общих методов, изменения статических методов и изменения блоков кода.

  • Обеспечить синхронное выполнение кода (т. е. взаимное исключение между разными потоками) (атомарность)
  • Обеспечение своевременной видимости изменений общих переменных (видимость)
  • Эффективно решить проблему перестановки инструкций (последовательно)

Принцип реализации

Используйте монитор объекта (Монитор, также называемый монитором) для управления

  • Выполнять инструкцию байт-кода MonitorEnter при входе/блокировке
  • Выход/разблокировка при выполнении инструкций по байт-коду MonitorExit
    • Когда код выполнения имеет ненормальный метод/сегмент кода выхода, он будет автоматически разблокирован.

Монитор какого объекта использовать:

  • При оформлении метода объекта используйте монитор текущего объекта.
  • При оформлении статических методов используйте монитор типа класса (объект класса)
  • При оформлении блока кода используйте наблюдатель объекта в скобках
    • Должен быть объектом объекта класса или его подклассов

MonitorEnter (заблокировано):

  • Каждый объект имеет связанный монитор.
  • Монитор заблокирован тогда и только тогда, когда у него есть владелец.
  • Поток выполняет MonitorEnter, чтобы стать владельцем монитора.
  • Если количество записей объекта Monitor (количество записей, количество повторных входов потока, которому он принадлежит) равно 0, Если установить его в 1, поток сделает себя владельцем объекта Monitor.
  • Если владельцем монитора является текущий поток, он повторно войдет в монитор и увеличит количество записей на единицу.
  • Если владельцем монитора является другой поток, текущий поток будет заблокирован до тех пор, пока количество записей не станет равным 0. Бороться за суверенитет.

Выход из монитора (разблокировано):

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

Для MonitorEnter и MonitorExit есть два основных параметра:

  • нить
  • Объект, с которым связан монитор

ключевая структура

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

переменная экземпляра

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

Ввод данных

  • Поскольку виртуальная машина требует, чтобы начальный адрес объекта был целым числом, кратным 8 байтам
  • Данные заполнения предназначены только для выравнивания байтов
    • Гарантирует, что начальный адрес следующего объекта является целым числом, кратным 8
  • длина может быть 0

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

  • Заголовок объекта состоит из Mark Word, Class Metadata Address (адрес метаданных класса) и длины массива (когда объект является массивом).
  • В 32-битных и 64-битных виртуальных машинах Mark Word занимает 32 байта и 64 байта соответственно, поэтому он называется word

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

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

Без блокировки -> Блокировка смещения -> Легкая блокировка -> Тяжелая блокировка

Среди них предвзятые блокировки и облегченные блокировки были представлены в JDK 6 и включены по умолчанию в JDK 6. Эскалация блокировок (раздувание блокировки, раздувание) является односторонней и может идти только от низкой к высокой (слева направо). Даунгрейд блокировок не происходит.

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

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

Режим смещения завершается, когда другой поток пытается получить блокировку. В зависимости от того, находится ли объект блокировки в настоящее время в заблокированном состоянии, после отмены смещения отмены он будет восстановлен в разблокированном состоянии (бит флага «01», который не может быть смещен) или в состояние облегченной блокировки (значение бит флага равен "00"). Операция синхронизации входит в процесс облегченной блокировки.

Легкий замок

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

тяжелый замок

Тяжелый замок достигается по теме системы Mutex, стоимость является самым дорогим.

ContentionList, CXQ, поток, удерживающий последнюю оспариваемую блокировку

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

EntryLis, с указанием группы победителей

  • Двусвязный список
  • Только поток, которому принадлежит блокировка, может получить доступ или изменить EntryLis.
  • Только поток, которому принадлежит блокировка, освобождает блокировку, и когда EntryList пуст, а ContentionList не Когда он пуст, все потоки в ContentionList могут быть исключены из очереди и помещены в EntryList.

WaitSet, который сохраняет поток в состоянии ожидания

  • Поместите поток, выполняющий вызов wait(), в WaitSet
  • Когда делается вызов notify(), notifyAll(), поток помещается в очередь ContentionList или EntryList.

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

  • Для потока не более одного из трех наборов в любой момент времени
  • Потоки в этих трех наборах находятся в состоянии BLOCKED, а нижний уровень использует мьютекс для блокировки.

Когда поток успешно получает блокировкуПоле владельца монитора объекта изменено с NULL на ненулевое, указывая на этот поток Должен исключить себя из ContentionList или EntryList

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

OnDeck,表示准备就绪的线程,保证任何时候都只有一个线程来直接竞争 Замок

  • При приобретении блокировки, если соревнование происходит, используйте спиновые замки для борьбы с, если все еще DUBU после вращения Чтобы, а затем в указанную очередь.
  • Spin может сократить работу CONTENTIONLIST и ENTRYLIST для входа в команду из команды, то есть внутренний Эти замки этих замков сохраняются.

Мьютекс ОС

Тяжеловесные блокировки реализуются через блокировки мьютексов потоков операционной системы.Под Linux для блокировок используется технология pthead_mutex_lock/pthead_mutex_unlock, то есть взаимоисключающие блокировки между потоками.

Мьютекс потока реализован на основе механизма фьютекса (Fast Userspace Mutex). Механизм синхронизации обычной операционной системы (например, IPC и т. д.) должен быть перехвачен в ядре для выполнения, даже если нет конкуренции, должна быть выполнена операция прерывания (int 0x80, trap). А фьютекс представляет собой смесь режима ядра и режима пользователя. Когда нет конкуренции, получение и снятие блокировок не обязательно должно быть захвачено ядром.

первоначальное распределение

Во-первых, выделите общую переменную Futex в памяти. Для потоков память совместно используется и может быть выделена напрямую (Malloc). Это целочисленный тип, а начальное значение 1.

получить замок

Используйте CAS для уменьшения переменной фьютекса на 1 и посмотрите на результат:

  • Если он меняется с 1 на 0, значит конкуренции нет, продолжайте выполнять
  • Если он меньше 0, это означает, что есть конкуренция, вызовите futex(..., FUTEX_WAIT, ...), чтобы заставить текущий поток спать

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

Используйте CAS для увеличения переменной фьютекса на 1

  • Если переменная FOTEX изменяется от 0 до 1, это означает, что нет конкуренции и продолжает выполнять
  • Если перед изменением переменная фьютекса имеет отрицательное значение, значит есть конкуренция, вызываем futex(..., FUTEX_WAKE, ...) разбудить один или несколько ожидающих потоков

Lock

Напишите еще одну статью, посвященную объяснению механизма блокировки AQS, с нетерпением жду