жизненный опыт
конвейер инструкций
Основная задача ЦП — выполнение сохраненной последовательности инструкций, программы. Процесс выполнения программы на самом деле представляет собой процесс непрерывного получения инструкций, анализа инструкций и выполнения инструкций.
Почти все процессоры компьютеров 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: передать значение переменной из рабочей памяти в основную память для последующих операций записи
- запись: записать значение переменной, полученное операцией сохранения, из рабочей памяти в переменную в основной памяти
Определить правила использования атомарных операций
- Потоку не разрешено синхронизировать данные из рабочей памяти в основную память без причины (операция присваивания не выполнялась)
- Новую переменную можно создать только в основной памяти, нельзя использовать неинициализированные (загрузить или присвоить) переменную непосредственно в рабочей памяти. То есть, прежде чем реализовывать операции использования и сохранения над переменной, операции присваивания и загрузки должны выполняться самостоятельно.
- Переменная может быть заблокирована только одним потоком одновременно, но операция блокировки может многократно выполняться одним и тем же потоком много раз.После многократного выполнения блокировки переменная будет разблокирована только после такого же количества разблокировок. операции выполняются. блокировка и разблокировка должны быть сопряжены.
- Если операция блокировки выполняется над переменной, значение переменной в рабочей памяти будет очищено.Прежде чем исполнительный механизм использует переменную, необходимо повторно выполнить операцию загрузки или назначения, чтобы инициализировать значение переменной.
- Если переменная ранее не была заблокирована операцией блокировки, ей не разрешается выполнять операцию разблокировки; также не разрешается разблокировать переменную, заблокированную другими потоками.
- Перед выполнением операции разблокировки переменной переменная должна быть синхронизирована с оперативной памятью (выполняются операции сохранения и записи)
Как видно из вышеизложенного, копирование переменных из основной памяти в рабочую память требует последовательного выполнения операций чтения и загрузки, а синхронизация из рабочей памяти обратно в основную память требует последовательного выполнения операций сохранения и записи. Суммировать:
- Читайте, нагрузка и использование должны появляться в парах, но они не обязаны появляться последовательно. Назначить, хранить, писать одинаково;
- Рождение и инициализация переменных: переменные могут быть «рождены» только из основной памяти и должны быть инициализированы перед тем, как их можно будет использовать, то есть загрузка/назначение должны выполняться перед использованием/сохранением;
- После блокировки переменной значение переменной в рабочей памяти будет очищено, и ее необходимо инициализировать перед использованием, перед разблокировкой переменная должна быть синхронизирована обратно в основную память;
- Переменная может быть заблокирована только одним потоком одновременно, и она должна быть разблокирована несколько раз, когда она заблокирована; незаблокированная переменная не может быть разблокирована, и поток не может разблокировать переменные, заблокированные другими потоками.
Специальные правила для переменных типа long и double
Модель памяти Java требует, чтобы вышеупомянутые 8 операций были атомарными, но для 64-битных типов данных long и double в модели специально определено свободное положение: виртуальной машине разрешено читать и записывать 64-битные данные, которые не модифицируется volatile.Разделите его на две 32-битные операции. То есть, когда он не изменяется с помощью volatile, чтение, прочитанное потоком, не является атомарной операцией, и он может только прочитать значение «полупеременной». Тем не менее, коммерческие виртуальные машины почти всегда реализуют чтение и запись 64-битных данных как атомарные операции, поэтому мы можем игнорировать эту проблему.
происходит до принципа
Модель памяти Java имеет некоторую присущую «упорядоченность», которая не требует какой-либо синхронизации с помощью (изменчивой, синхронизированной и т. д.), сможет обеспечить упорядоченный характер, это часто называют принципом «происходит до».
Если порядок выполнения двух операций не соответствует принципу упреждения и не может быть выведен из принципа «происходит раньше», то они не могут гарантировать их порядок, и виртуальная машина может изменить их порядок по своему усмотрению.
- Правило порядка выполнения программы: в потоке операции, написанные логически впереди, выполняются до операций, написанных позже.
- Правило блокировки (правило блокировки монитора): сначала выполняется операция разблокировки перед последующей операцией блокировки для той же блокировки. «Следование» относится к хронологическому порядку.
- правила изменяемой переменной(Правило энергозависимой переменной): Операция записи в энергозависимую переменную выполняется перед последующей операцией чтения этой переменной. «Следование» относится к хронологическому порядку.
- Транзитивность: если операция A встречается в операции B, операция B возникает в операции C, и операция A может быть нарисована первой в операции C.
- Правило запуска потока: метод start() объекта Thread предшествует каждому действию этого потока.
- Правило прерывания потока: вызов метода прерывания() потока происходит первым, когда код прерванного потока обнаруживает возникновение события прерывания (обнаруженного функцией Thread.interrupted()).
- Правило завершения потока: все операции в потоке выполняются первыми при обнаружении завершения потока.Мы можем определить, что поток завершил выполнение, через конец метода Thread.join() и возвращаемое значение Thread.isAlive().
- Правило финализации объекта (правило финализатора): инициализация объекта (конец выполнения конструктора) происходит сначала в начале его метода 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, с нетерпением жду