Галерея диаграмм связей Инструмент блокировки параллелизма Java
Это интеллектуальная карта инструментов блокировки параллелизма в Java. Эти инструменты блокировки параллелизма могут сыграть важную роль в параллельном программировании на Java и помочь вам писать эффективный и безопасный параллельный код.
Отредактировано в 2024-01-18 10:28:15Инструмент блокировки параллелизма Java
АннотацияQueueSynchronizer
Введение
Базовая структура для создания блокировок или других компонентов синхронизации, в основном используемая для управления статусом синхронизации.
Основной способ его использования — наследование.
Блокировка может произойти только в один момент, что снижает стоимость переключения контекста.
наследовать
АннотацияOwnableSynchronizer
Базовая реализация синхронизатора, позволяющая потокам переходить в монопольный режим.
Переменные-члены
частный временный потокexclusiveOwnerThread
Поток, в данный момент находящийся в состоянии синхронизации
основной метод
protected Final void setExclusiveOwnerThread (поток потока)
Установить эксклюзивную тему
защищенный финальный поток getExclusiveOwnerThread()
Получить текущую эксклюзивную тему
реализовать интерфейс
Сериализуемый
внутренний класс
статический конечный класс Node
Введение
AQS использует двустороннюю очередь синхронизации FIFO для внутреннего управления состоянием синхронизации. Если поток не удается получить, он добавляется в конец очереди. Структура очереди определяется как головной и хвостовой узлы; в АКС. Очередь условий также использует определение Node, но находится в другой очереди.
Переменные-члены
статическая константа
статический конечный Node SHARED = новый Node()
Указывает, что узел находится в общем режиме.
статический окончательный узел ЭКСКЛЮЗИВ = ноль
Указывает, что узел находится в монопольном режиме и только один поток одновременно удерживает состояние синхронизации.
статический финал int CANCELED = 1
Узел (поток) находится в этом состоянии из-за таймаута или прерывания и впоследствии не перейдет в другие состояния, а также не должен больше блокироваться.
статический финал int SIGNAL = -1
Когда узел (поток) находится в этом состоянии, узел-преемник находится или будет в заблокированном состоянии (через парковку), поэтому, когда текущий узел снимает блокировку или отменяется, необходимо вызвать команду unpark, чтобы разбудить узел-преемник. .
статический окончательный int CONDITION = -2
Указывает, что текущий узел (поток) находится в очереди условий (через ожидание) и не является частью очереди синхронизации до тех пор, пока в определенный момент статус не будет установлен на 0 (посредством сигнала)
статический финал int PROPAGATE = -3
Распространение общих блокировок (используется головным узлом)
изменчивый int waitStatus
Включая приведенное выше CANCELLED/SIGNAL/CONDITION/PROPAGATE и 0 (указывающее, что он не находится в этих четырех состояниях), неотрицательное число указывает на то, что узел не требует взаимодействия.
энергозависимый узел предыдущий
узел-предшественник
энергозависимый узел следующий
узел-преемник
летучая нить
Поток, соответствующий узлу
Узел следующийОфициант
В очереди синхронизации он указывает, находится ли узел в общем или эксклюзивном состоянии, равном SHARED, чтобы указать совместное использование, и нулю, чтобы указать эксклюзивное состояние в условной очереди, это указывает на ссылку на следующий узел;
Условие можно использовать только в том случае, если очередь синхронизации находится в монопольном режиме, поэтому эта переменная предназначена для совместного использования.
основной метод
Метод строительства
Узел()
Используется для создания начальных узлов или создания ОБЩИХ узлов.
Узел (Резьба, режим узла)
Для использования в addWaiter
Node (поток потока, int waitStatus)
Б/у в состоянии
метод-член
последнее логическое значение isShared()
вернуть следующийWaiter == ОБЩИЙ
последний предшественник узла()
Получить узел-предшественник, выдать исключение, если оно равно нулю.
публичный класс ConditionObject
Введение
Реализация интерфейса Condition служит базовой реализации Lock. Используйте Node для создания очереди ожидания. Каждый объект Condition содержит очередь FIFO (одностороннюю).
Очередь использует свойство nextWaiter узла Node в качестве ссылки на следующий узел.
реализовать интерфейс
Состояние
Сериализуемый
Переменные-члены
статическая константа
частный статический финал int REINTERRUPT = 1
Указывает на необходимость снова прерваться при выходе из ожидания
частный статический финал int THROW_IE = -1
Указывает, что InterruptedException необходимо создавать при выходе из ожидания.
частный переходный узел firstWaiter
Указывает на головной узел условной очереди.
частный переходный узел LastWaiter
Указывает на хвостовой узел очереди условий.
основной метод
публичный финал void await()
Заставьте вызывающий поток войти в очередь ожидания, снимите блокировку и войдите в состояние блокировки. После пробуждения узла сигналом/signalAll попытайтесь получить блокировку. Если блокировка пробуждена прерыванием, он отреагирует на прерывание.
частный узел addConditionWaiter()
Добавьте новый узел в очередь ожидания.
Конкретная логика реализации: когда обнаруживается, что LastWaiter отменен, вызовите unlinkCancelledWaiters, чтобы очистить отмененные методы во всей очереди (как firstWaiter, так и LastWaiter могут указывать на новые узлы, затем создайте новый узел (состояние CONDITION) и добавьте его в список; конец очереди
частная пустота unlinkCancelledWaiters()
Используйте while для очистки узлов, у которых waitStatus не является CONDITION, начиная с firstWaiter. Поскольку вызов метода происходит до снятия блокировки, блокировать циклический процесс не требуется. Чтобы избежать остатков в GC при отсутствии сигнала, этот метод используется только при возникновении тайм-аута или отмены.
WaitStatus узла в очереди условий должен иметь только два состояния: CONDITION и CANCELLED.
Final int FullRelease (узел Node)
Освободите состояние блокировки. Если состояние блокировки не удается освободить, будет выдано исключение IllegalMonitorStateException, и узел будет переведен в состояние CANCELED. В случае успеха будет возвращено значение состояния до освобождения.
выпускать
Здесь вызывается метод освобождения AQS и находится подходящий узел для пробуждения.
В параметрах используется атрибут состояния AQS.
последнее логическое значение isOnSyncQueue (узел узла)
Определите, находится ли узел в очереди синхронизации (обратите внимание, что это очередь синхронизации, а не условная очередь): когда статус ожидания равен CONDITION, это означает, что он не находится в очереди синхронизации, если предыдущий или следующий узел находится в этом состоянии. null, это также означает, что его нет в очереди синхронизации (эти два атрибута используются для очереди синхронизации. Предшественник и преемник условной очереди не используют эти два атрибута), а затем вызовите метод findNodeFromTail для прохождения синхронизации. очередь из хвостового узла, чтобы узнать, находится ли в нем этот узел.
Связь между очередью синхронизации и очередью условий: конкуренция блокировок зависит только от очереди синхронизации. Очередь условий сохраняет только потоки, заблокированные из-за отсутствия условий. Когда потокам необходимо конкурировать за блокировки, их все равно необходимо преобразовать в очередь синхронизации.
Этот метод используется в качестве условия определения while в коде. Если узел не находится в очереди синхронизации (что указывает на то, что он не является signal/signalAll), он войдет в while и заблокируется через парк. Когда узел пробуждается, он сначала определяет, был ли он разбужен прерыванием. Если нет, он продолжает возвращаться к процессу while. В противном случае он попытается добавить узел в очередь синхронизации и выйти из режима while. процесс.
acquireQueued(узел, saveState)
Постоянно пытается получить статус синхронизации saveState для узлов узла в очереди синхронизации.
отменить связьОтмененоОфицианты
Если после получения блокировки node.nextWaiter не имеет значения null, вызовите этот метод, чтобы очистить узел в отмененном состоянии.
отчетInterruptAfterWait
Если ранее он был разбужен из-за прерывания, прерывание необходимо обработать на основе результатов предыдущего решения: сбросить ли статус прерывания или вызвать исключение прерывания.
публичный финал long awaitNanos(long nanosTimeout)
Основная логика аналогична await, с добавлением оценки тайм-аута.
Я не совсем понимаю последнюю часть TransferAfterCancelledWait.
публичное окончательное логическое ожидание (долгое время, единица времени TimeUnit)
Базовая логика аналогична awaitNanos, вы можете указать единицу времени, и в конечном итоге для расчета она преобразуется в наносекунды.
публичный окончательный void awaitUninterruptible()
Базовая логика аналогична await и не реагирует на прерывания (то есть selfInterrupt сбрасывает статус прерывания).
публичное окончательное логическое значение awaitUntil (крайний срок)
Основная логика аналогична ожиданию, с добавлением максимального времени блокировки.
публичный окончательный сигнал void()
Переместить самый длинный ожидающий поток из условной очереди в очередь синхронизации.
защищенное логическое значение isHeldExclusively()
Возвращает, удерживает ли текущий поток блокировку в монопольном режиме, если да, возвращает true. Этот метод находится в AQS и не предусматривает конкретной реализации. Однако, поскольку этот метод используется только в условии, его не нужно реализовывать, если ConditionObject не используется, в противном случае его необходимо реализовать.
Private void doSignal (сначала узел)
Удалите узлы из очереди условий и преобразуйте их в узлы из очереди синхронизации. Процесс реализации представляет собой цикл спереди назад, пока не встретится узел, который не равен нулю и не находится в состоянии CANCELED, преобразует его и выходит из цикла.
окончательное логическое значение TransferForSignal (узел узла)
Преобразуйте узел из условной очереди в очередь синхронизации и сообщите, было ли преобразование успешным. Логика реализации заключается в том, чтобы сначала определить, является ли статус узла УСЛОВИЕ. Если это не отмененный узел, вернуть false, затем вызвать enq, чтобы войти в очередь синхронизации и получить узел-предшественник узла в очереди. состояние узла-предшественника > 0 или CAS изменен. Если waitStatus завершается неудачно (произошли изменения), отпаркуйте поток и, наконец, верните true
Он не обязательно пробуждается сразу после присоединения к очереди синхронизации. Только когда waitStatus>0 или изменения во время выполнения CAS, поток будет разпаркован и пробуждён. Если вы не проснетесь в это время, вы дождетесь синхронизации логики очереди, а затем проснетесь.
запрос
публичный окончательный недействительный сигналAll()
Перенесите все квалифицированные (не отмененные) узлы из очереди условий в очередь синхронизации. Основная логика аналогична сигналу, разница в том, что метод TransferForSignal выполняется в цикле.
isHeldExclusively
частная пустота doSignalAll (сначала узел)
TransferForSignal
защищенное окончательное логическое значение hasWaiters()
Если в очереди условий есть потоки, метод реализации заключается в циклическом цикле очереди условий с самого начала. Если существует узел со статусом CONDITION, он возвращает true, в противном случае он возвращает false.
Переменные-члены
статическая переменная
статический окончательный длинный spinForTimeoutThreshold = 1000L;
Порог, используемый для таймаута. Если он меньше этого значения, нет необходимости вызывать парк с таймаутом, но позвольте циклу продолжать выполнение. В это время эффективность вращения выше.
частный статический финал Небезопасно небезопасно
Unsafe.getUnsafe(), следующие элементы используются для получения смещения с помощью метода objectFieldOffset, который облегчает последующие изменения непосредственно с помощью операции CAS Unsafe.
частный статический окончательный длинный stateOffset
состояние AQS
частный статический окончательный длинный headOffset
руководитель AQS
частный статический окончательный длинный хвостOffset
Хвост AQS
частный статический окончательный длинный waitStatusOffset
Статус ожидания узла
частный статический окончательный длинный nextOffset
Узел следующий
частный временный энергозависимый головной узел
Головной узел очереди синхронизации, отложенная загрузка, модифицируется только через setHead, когда головной узел существует, его статус не может быть ОТМЕНЕН;
частный временный энергозависимый хвост узла
Хвостовой узел очереди синхронизации, отложенная загрузка
частное нестабильное внутреннее состояние
Статус синхронизации
основной метод
защищенный окончательный int getState()
Возвращает текущее значение статуса синхронизации.
защищенный окончательный недействительный setState (int newState)
Установить текущий статус синхронизации
защищенное окончательное логическое сравнениеAndSetState (int ожидаемое, int обновление)
CAS устанавливает состояние, а нижний уровень вызывает метод CAS Unsafe.
частный узел addWaiter (режим узла)
метод постановки в очередь
Добавить текущий поток в конец очереди, используя указанный режим.
Стоит отметить, что
Параметр Node используется для указания режима, а поток получается через currentThread.
В реализации, когда хвост не пуст, быстро попытайтесь установить хвост. В случае успеха он вернется напрямую. В случае неудачи будет вызван метод enq.
частный узел enq (последний узел узла)
Цикл (вращение) CAS. Когда хвост пуст, CAS сначала инициализирует заголовок (новый узел). Когда хвост не пуст, CAS устанавливает хвост.
Узел параметра здесь — это узел, который необходимо добавить, а не режим.
публичное окончательное логическое значение hasQueuedPredecessors()
Определите, есть ли узел перед текущим потоком, если да, верните true, в противном случае верните false. Используется для реализации честных замков. Конкретная реализация: когда очередь не пуста, она возвращает true, если решено, что узел-преемник заголовка пуст или не является текущим потоком.
Когда head.next становится нулевым?
публичное окончательное логическое значение hasQueuedThreads()
return head != Tail; есть ли потоки, ожидающие в очереди
общедоступное окончательное логическое значение isQueued (поток потока)
Независимо от того, находится ли поток в очереди, реализация должна выполняться от конца к началу.
публичный финал int getQueueLength()
Получить длину очереди. Метод реализации заключается в цикле от конца к началу для вычисления количества узлов, поток которых не равен нулю.
публичная финальная коллекция <Thread> getQueuedThreads()
Получает коллекцию потоков в очереди и возвращает ее в форме Collection. Реализация заключается в создании ArrayList, цикле от конца к началу для добавления потоков, которые не являются нулевыми, а затем возврата объекта ArrayList.
общедоступное окончательное логическое значение имеет (условие ConditionObject)
Принадлежит ли объект условия текущему синхронизатору AQS
эксклюзивный режим
Только один поток может одновременно получить статус синхронизации.
публичное окончательное недействительное приобретение (int arg)
Эксклюзивный режим получает статус синхронизации и игнорирует прерывания; сначала вызовите tryAcquire, чтобы попытаться получить монопольную блокировку, а после неудачи вызовитеacquireQueued(addWaiter(Node.EXCLUSIVE), arg), чтобы попытаться добавить поток в очередь синхронизации и определить его. состояние узла, а затем на основе возвращенного результата (есть ли (прерывание потока) вызвать метод прерывания потока, чтобы восстановить состояние прерывания
сам процесс захвата не реагирует на прерывания, поэтому обработка прерываний заключается в восстановлении статуса прерывания.
защищенное логическое значение tryAcquire(int arg)
Попробуйте получить монопольную блокировку, верните true в случае успеха и false в случае неудачи. Конкретная реализация завершается подклассами, и при получении статуса синхронизации необходимо обеспечить безопасность потоков.
Final Boolean AcquireQueued (конечный узел Node, int arg)
arg можно понимать как ресурс, который необходимо конкурировать, AQS внутренне представлен состоянием, но на самом деле конкретное использование определяется разработчиком.
Постоянно пытаться получить блокировки для потоков, уже находящихся в очереди (узел создан addWaiter)
Логика реализации: Прокрутка выглядит следующим образом: если узел-предшественник является головным и успешно получает блокировку, установите текущий узел в качестве головного и возврат не прерывается, в противном случае перейдите к следующим операциям: вызов mustParkAfterFailedAcquire для определения и установки статуса узла и вызовите parkAndCheckInterrupt, чтобы заблокировать поток и проверить статус прерывания. Если во время процесса возникает исключение, вызовите метод cancelAcquire, чтобы отменить получение.
Фактически, даже если блокировка не получена, цикл не остановится. Когда статус узла-предшественника успешно установлен и поток прерывается с парковкой, поток приостанавливается; Узел после заголовка будет использовать tryAcquire, чтобы конкурировать с новым потоком за блокировку, указывая на то, что реализация блокировки здесь несправедлива.
частный void setHead (узел узла)
Установите для узла заголовок и установите для атрибутов thread и prev значение null, чтобы облегчить сборку мусора. Фактически это эквивалентно исключению из головы очереди.
частное статическое логическое значение mustParkAfterFailedAcquire(Node pred, Node node)
Проверьте и обновите статус узла-предшественника узла, которому не удалось получить блокировку SIGNAL, и проверьте, нужно ли блокировать текущий поток.
Логика реализации: Определите статус ожидания узла-предшественника и верните true, если он равен СИГНАЛ; когда >0, это означает, что узел-предшественник был отменен, и узел-предшественник будет пропущен в цикле до тех пор, пока не будет достигнут статус ожидания узла-предшественника. <=0, а затем вернуть false; в противном случае CAS Установите для параметра waitStatus узла-предшественника значение SIGNAL и верните false;
частный окончательный логический паркAndCheckInterrupt()
Вызов парковки, проверка статуса прерывания и возврат
return thread.interrupted() Возвращаемый здесь результат используется для определения того, был ли поток разбужен из-за прерывания. Если он был разбужен из-за прерывания, он все равно будет находиться в цикле в AcquireQueued и снова будет заблокирован парковкой (). поскольку метод прерывания() очищает флаг прерывания, поэтому при пробуждении от unpark блокировка будет получена, просто проверьте еще раз во внешнем цикле;
Что вызывает ложное возбуждение?
частная пустота cancelAcquire (узел узла)
Вызывается, когда весь метод генерирует исключение, очищает поток от узла, устанавливает статус CANCELLED и находит узел с waitStatus<=0 в качестве предшественника, удаляет себя из очереди и при необходимости пробуждает следующий; подходящий узел
Private void unparkSuccessor (узел Node)
Следует отметить, что если следующий узел не пуст, снимите его с парковки; если следующий узел имеет значение null, начните с хвоста и найдите самый передний узел с waitStatus<=0, который не является узлом от хвоста до передняя часть.
Причина, по которой мы выполняем поиск сзади вперед, заключается в том, что установка предшественника и преемника двусвязного списка не является атомарной операцией. Может случиться так, что следующий узел пуст, но уже находится в очереди, или узел только что появился. был отменен и его следующий указывает на себя, и обход просто доходит до этого узла.
public Final void AcquireInterruptible(int arg)
Чтобы получить статус синхронизации в монопольном режиме в ответ на прерывание, будет использоваться Thread.interrupted(), чтобы определить, была ли она прервана перед tryAcqure. Если да, то будет выдано исключение.
попробоватьПолучить
сначала попробуй один раз
частная пустота doAcquireInterruptily (int arg)
Основная логика такая же, как и методacquireQueued. Разница в том, что addWaiter вызывается внутренне и не возвращает информацию о прерывании потока, а напрямую выдает InterruptedException после проверки того, что парк пробуждается из-за прерывания.
отменитьПолучить
общедоступное окончательное логическое значение tryAcquireNanos(int arg, long nanosTimeout)
Эксклюзивное получение статуса синхронизации с таймаутом, реагирование на прерывания. Если статус синхронизации не получен в течение периода ожидания, возвращается false.
попробоватьПолучить
сначала попробуй один раз
частное логическое значение doAcquireNanos(int arg, long nanosTimeout)
Основная логика такая же, как и doAcquireInterruptible, с добавлением логики оценки времени и использованием порога spinForTimeoutThreshold.
отменитьПолучить
публичный окончательный логический выпуск (int arg)
Эксклюзивный режим освобождает состояние синхронизации. Сначала вызывается tryRelease, чтобы попытаться изменить состояние. После успеха считается, что head != null и head.waitStatus!=0 (значение waitStatus здесь равно 0, что указывает на пустую очередь). равно 0, это означает, что очередь пуста. При вызове unparkSuccessor вызывается для пробуждения последующих узлов, а затем возвращает true; tryRelease завершается с ошибкой и возвращает false.
защищенное логическое значение tryRelease(int arg)
Попробуйте изменить состояние. В AQS нет конкретной реализации, и подклассы должны реализовать ее самостоятельно. Для этого метода используется переменная arg метода Release, возвращаемое значение — успешно ли освобождено состояние;
Описание arg см. в разделе реализации ниже.
разпарковать преемника
Режим обмена
Несколько потоков получают статус синхронизации одновременно
public Final void AcquireShared(int arg)
Общий режим приобретает статус синхронизации и игнорирует прерывания. Реализация: сначала вызовите tryAcquireShared, чтобы попытаться получить статус синхронизации. После неудачи (результат меньше 0) вызовите doAcquireShared, чтобы получить статус синхронизации.
защищенный int tryAcquireShared (int arg)
Попытайтесь получить статус синхронизации. В AQS не предусмотрен конкретный метод, который реализуется в подклассах. Возвращаемое значение меньше 0 указывает, что получение не удалось; значение, равное 0, указывает, что получение было успешным, но никакое другое получение в совместном режиме не было успешным; значение больше 0 указывает, что получение было успешным, и другие приобретения в режиме совместного использования также могут быть успешными.
частная пустота doAcquireShared (int arg)
После присоединения к очереди spin пытается получить статус синхронизации. Основная логика в основном такая же, как и в методеacquireQueued в монопольном режиме. Разница в том, что когда предшественник узла является головным, необходимо оценить результат, возвращаемый tryAcquireShared. оно больше или равно 0 (указывает, что ресурсы все еще есть и могут продолжать распространяться), затем вызовите setHeadAndPropagate, чтобы установить атрибуты заголовка и распространения (установите новый заголовок и оцените узел-преемник и разбудите его, если необходимо). ; в противном случае вам все равно придется вызывать mustParkAfterFailedAcquire и parkAndCheckInterrupt для последующих операций (парковка и последующее принятие решения о прерывании и т. д.). Кроме того, в этом методе также выполняется метод selfTninterrupt для установки статуса прерывания.
попробуйтеAcquireShared
Если предшественником узла является голова, попробуйте сначала его.
Private void setHeadAndPropagate (узел узла, распространение int)
Воплощение общения. Фактически этот метод проверяет узел-преемник узла, который в данный момент получает блокировку, то есть может быть один раз распространен назад. Вначале мне было интересно, почему я не нашел никакого кода, похожего на пробуждение в обратном направлении в цикле. Позже я обнаружил, что сам setHeadAndPropagate находится в for(;;). Каждый пробужденный узел выполнит этот метод, если получит блокировку. , поэтому это достигает цели обратного распространения один за другим. Обратите внимание, что они не пробуждаются вместе, а затем соревнуются, а пробуждаются один за другим.
Продолжайте пробуждать узлы в обратном порядке. Этот метод будет вызываться только тогда, когда tryAcquireShared вернет результат r>=0 (указывающий, что ресурсы все еще доступны), а r передается в метод в качестве параметра распространения. Этот метод установит узел в качестве заголовка, а затем определит, можно ли передать его обратно. Если да, вызовите doReleaseShared, чтобы разбудить следующий узел.
Логика суждения: распространять>0 (указывает на наличие разрешения) Или предыдущая голова == ноль Или предыдущий head.waitStatus <0 Или текущая голова (то есть узел) == null Или теперь head.waitStatus <0 Затем перейдите к следующему шагу: node.next == ноль или node.next.isShared()
На самом деле не очень понятно: 1. Зачем нам определять текущий напор ((h=head)== null) 2. Почему doReleaseShared по-прежнему требуется после node.next == null
Предположение 1: поскольку head может быть изменен другими потоками в процессе принятия решения, если новый head по-прежнему соответствует этому условию, вы все равно можете продолжить; я чувствую, что логика решения здесь больше похожа на то, что я не знаю, почему head имеет значение null, но я все еще пробую консервативную стратегию обработки, тогда 2 может быть аналогичной консервативной стратегией.
Считается, что waitStatus<0 связано только с тем, что состояние головки может измениться на SIGNAL, а CONDITION здесь не появится.
частная пустота doReleaseShared()
Вращение пробуждает узел-преемник, который отличается от независимого режима тем, что ему также необходимо обеспечить свойства распространения. Реализация: Цикл: Когда очередь не пуста, определите waitStatus заголовка. Когда он равен SIGNAL, CAS устанавливает waitStatus в 0. В случае успеха будет вызван unparkSuccessor(head) для пробуждения узла-преемника. В случае неудачи. , начать сначала; значение waitStatus равно 0, а CAS устанавливает значение waitStatus в 0. При ошибке PROPAGATE начать заново, затем оценить head==h, чтобы увидеть, был ли изменен головной узел, и если да, то выйти из цикла.
Обратите внимание, что этот метод не передает параметры и запускается непосредственно из головы.
Предположение: Узел общего режима может иметь только три состояния: 0 (только что создан), РАСПРОСТРАНЯТЬ и СИГНАЛ.
разпарковать преемника
отменитьПолучить
public Final void AcquireSharedInterruptible(int arg)
Общий режим получает статус синхронизации и поддерживает прерывания аналогично эксклюзивному режиму.
попробуйтеAcquireShared
частная пустота doAcquireSharedInterruptily (int arg)
Подобно логике doAcquireShared, исключение выдается после обнаружения прерывания.
отменитьПолучить
общедоступное окончательное логическое значение tryAcquireSharedNanos(int arg, long nanosTimeout)
Общий режим с периодом ожидания получает статус синхронизации и поддерживает прерывание.
попробуйтеAcquireShared
частное логическое значение doAcquireSharedNanos(int arg, long nanosTimeout)
Логика определения тайм-аута аналогична эксклюзивному режиму, а другая логика выполнения аналогична doAcquireShared.
отменитьПолучить
публичный окончательный логический релизShared(int arg)
Общий режим освобождает состояние синхронизации
защищенное логическое значение tryReleaseShared(int arg)
Пустой метод, реализованный подклассами, пытается освободить состояние синхронизации. arg является параметром ReleaseShared и может быть определен вами; возвращаемое значение равно true, когда этот метод успешно освобождает состояние синхронизации и другие потоки могут его получить, в противном случае он возвращает false.
doReleaseShared
Здесь мы по-прежнему только пробуждаем последующий узел, а последующее распространение пробуждения осуществляется пробудившимся узлом.
Как реализовать собственную блокировку на базе AQS
конкурирующие ресурсы
AQS использует состояние частной переменной для абстрактного представления ресурсов для конкуренции блокировок (например, 1 означает занято, 0 означает не занято)
Операции с состоянием требуют использования соответствующих методов: getState, setState, CompareAndSetState и т. д.
Метод внедрения по требованию
tryAcquire, tryAcquireShared, tryRelease, tryReleaseShared, isHeldExclusively
Параметр arg различных методов в AQS в конечном итоге передается в эти методы, поэтому эти методы определяют, требуется ли параметр arg и как его использовать. В комментариях к исходному коду это означает «может представлять что угодно».
Эксклюзивная блокировка (независимый режим)
защищенное логическое значение tryAcquire(int arg);
Реализация запросов и получение конкурентных ресурсов обычно завершается работой CAS состояния. Входящие параметры могут быть определены разработчиком.
Простой пример: защищенное логическое значение tryAcquire(int приобретает) { Assert приобретает == 1 // Определить, что входящий параметр может быть только 1; если (compareAndSetState(0, 1)) { вернуть истину; } вернуть ложь; } protected boolean tryRelease(int Releases) { Assert Releases == 1 // Определить, что входящий параметр может быть только 1; if (getState() == 0) создать новое исключение IllegalMonitorStateException(); УстановитьСостояние (0); вернуть истину; }
защищенное логическое значение tryRelease(int arg);
Чтобы освободить конкурирующие ресурсы, переменная состояния обычно уменьшается или обнуляется. Входящие параметры могут быть определены разработчиком.
Внутри метода не должно быть никаких блокировок или ожиданий.
общий замок
защищенный int tryAcquireShared (int приобретает)
Входящие параметры определяются разработчиком и могут рассматриваться как количество ресурсов, которые они хотят получить; возвращаемое значение int можно рассматривать как оставшееся количество общих ресурсов.
Простой пример: protected int tryAcquireShared(int приобретает) {//несправедливо для (;;) { int доступно = getState(); int оставшийся = доступный - приобретает; если (осталось < 0 || CompareAndSetState(доступно, осталось)) вернуть оставшееся; } } protected boolean tryReleaseShared(int Releases) { для (;;) { int current = getState(); int next = текущие выпуски; если (compareAndSetState(текущий, следующий)) вернуть истину; } }
защищенное логическое значение tryReleaseShared(int Releases)
Входящие параметры определяются самими разработчиками и могут рассматриваться как количество ресурсов, которые они хотят выпустить.
переменная условия
Хотя логика реализации AQS не использует состояние, при использовании переменных состояния состояние будет использоваться в качестве входящего параметра выпуска (int arg) по умолчанию, поэтому часто необходимо изменить переменную состояния при реализации этих методов.
защищенное логическое значение isHeldExclusively()
Возвращает, занимает ли текущий поток исключительно блокировку мьютекса, соответствующую условной переменной.
Распространенные методы реализации: защищенное логическое значение isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } защищенное логическое значение tryAcquire(int приобретает) { ... setExclusiveOwnerThread(Thread.currentThread()); вернуть истину; }
Повторный входЧтениеЗаписьБлокировка
Реентерантлокок
Введение
Реентерабельные блокировки делятся на два метода: справедливые блокировки и нечестные блокировки.
реализовать интерфейс
Замок
Сериализуемый
внутренний класс
абстрактный статический класс Sync
Абстрактный класс является основой реализации ReetrantLock.
наследовать
АннотацияQueuedSynchronizer
основной метод
абстрактная недействительная блокировка()
Это абстрактный метод получения блокировки? Должен ли он быть реализован в подклассе?
окончательное логическое значение nonfairTryAcquire(int приобретает)
Нечестная реализация tryLock Конкретная реализация такова: CAS получает блокировку, когда ни один поток в данный момент не получает блокировку. В случае успеха поток, эксклюзивный для блокировки, устанавливается в текущий поток; если текущий поток уже удерживает блокировку, количество текущих блокировок обновляется. В этих двух случаях будет возвращено значение true, указывающее, что блокировка удерживается, а в остальных случаях будет возвращено значение false.
Так почему же несправедливая реализация должна быть помещена в родительский класс Sync? Поскольку этот метод используется как справедливыми, так и нечестными блокировками, в обоих случаях tryLocks основаны на этой реализации.
защищенное окончательное логическое значение tryRelease(int Releases)
Реализация tryRelease в AQS заключается в том, чтобы сначала вычислить общее количество блокировок минус количество снятых блокировок, чтобы получить количество оставшихся удержанных блокировок c. Если c равно 0, очистить состояние, установить состояние в значение c и выполнить возврат; не удерживаются ли замки.
защищенное окончательное логическое значение isHeldExclusively()
isHeldExclusively реализован в AQS, возвращает getExclusiveOwnerThread() == Thread.currentThread()
окончательный int getHoldCount()
Возвращает количество блокировок, удерживаемых текущим потоком. Этот метод основан на решении isHeldExclusively.
последнее логическое значение isLocked()
return getState() != 0; удерживает ли поток блокировку
последний поток getOwner()
Возвращает поток, удерживающий блокировку, или ноль, если нет
окончательный объект ConditionObject newCondition()
новоеСостояниеОбъект()
частный void readObject(java.io.ObjectInputStream s)
Метод десериализации, установите состояние в 0
статический финальный класс FairSync
Класс реализации справедливой блокировки
наследовать
Синхронизировать
основной метод
окончательная недействительная блокировка()
приобрести(1)
Параметр 1 здесь имеет практическое значение, отличное от CountDownLatch.
защищенное окончательное логическое значение tryAcquire(int приобретает)
TryAcquire реализация справедливой блокировки. Процесс реализации таков: когда занятая блокировка c равна 0, если впереди нет потока и cas успешно устанавливает c в состояние захвата, затем устанавливает текущий поток как монопольный поток и возвращает true, если текущий поток уже удерживает блокировку; обновить количество блокировок и вернуть true. Во всех остальных случаях возвращается false;
hasQueuedPredecessors
Здесь больше суждения, чем несправедливой блокировки. Даже если ни один поток в данный момент не получил блокировку, если поток ожидает более длительное время, он откажется от получения блокировки.
статический финальный класс NonfairSync
Класс реализации недобросовестной блокировки
наследовать
Синхронизировать
основной метод
окончательная недействительная блокировка()
Сначала попробуйте использовать прямой cas(0,1). Если это удалось, это означает, что никто другой в данный момент не получает блокировку. (1)
Фактически, метод нечестной блокировки tryAcquire также будет пытаться использовать cas. Прямой cas здесь может указывать на то, что более ранние вызовы могут получить ресурсы раньше.
защищенное окончательное логическое значение tryAcquire(int приобретает)
вернуть неfairTryAcquire(приобретает) Нечестная реализация уже существует в Sync, вызовите ее напрямую.
Переменные-члены
частная финальная синхронизация
Указывает, реализована ли блокировка справедливо или несправедливо, и соответствует FairSync или NonfairSync после создания экземпляра.
Конструктор
публичный ReentrantLock()
Несправедливая блокировка, созданная по умолчанию
public ReentrantLock (логическая ярмарка)
Постройте справедливую или нечестную блокировку в соответствии со справедливым выбором.
основной метод
публичная недействительная блокировка()
синхронизация.блокировка()
общественная недействительная блокировкаInterruptible()
sync.acquireInterruptible(1)
общедоступная логическая функция tryLock()
return sync.nonfairTryAcquire(1); Если блокировка успешно получена или текущий поток уже удерживает блокировку, возвращается true, в противном случае возвращается false.
Независимо от того, какой здесь замок, это нечестная попытка завладеть им.
общедоступное логическое значение tryLock (длительный тайм-аут, единица измерения TimeUnit)
вернуть sync.tryAcquireNanos(1, unit.toNanos(таймаут))
tryAcquireNano вызывает tryAcquire, поэтому существует разница между справедливостью и несправедливостью.
публичная недействительная разблокировка()
синхронизация.выпуск(1)
публичное условие newCondition()
вернуть sync.newCondition()
публичный int getHoldCount()
return sync.getHoldCount() Количество блокировок, удерживаемых текущим потоком.
общедоступное логическое значение isHeldByCurrentThread()
верните sync.isHeldExclusively(), удерживает ли текущий поток блокировку
публичное логическое значение isLocked()
вернуть sync.isLocked()
публичное окончательное логическое значение isFair()
вернуть экземпляр синхронизации FairSync
защищенный поток getOwner()
вернуть sync.getOwner()
публичное окончательное логическое значение hasQueuedThreads()
вернуть sync.hasQueuedThreads(), есть ли потоки в очереди
общедоступное окончательное логическое значение hasQueuedThread (поток потока)
return sync.isQueued(thread); находится ли текущий поток в очереди
публичный финал int getQueueLength()
вернуть sync.getQueueLength();
защищенная коллекция<Thread> getQueuedThreads()
вернуть sync.getQueuedThreads();
общедоступное логическое значение hasWaiters (условие)
Есть ли потоки, ожидающие данного условия (соответствует тому, есть ли потоки в состоянии CONDITION в очереди условий)
public int getWaitQueueLength (условие)
Количество потоков в состоянии CONDITION в очереди условий для данного условия.
защищенная коллекция <Thread> getWaitingThreads (условие)
Состояние
Введение
Переменные состояния интерфейса в основном используются для управления зависимостью выполнения потока от определенных состояний, сопоставления методов монитора объекта (wait/notify/notifyAll) с управлением объектами и объединения их с использованием любой реализации блокировки для достижения каждого объекта. эффект нескольких наборов ожидания. Это эквивалентно замене синхронизации с блокировкой и замене метода монитора с условием, то есть условие необходимо использовать вместе с блокировкой.
Основные различия между методами состояния и мониторинга: 1. Условие поддерживает несколько очередей ожидания, и один экземпляр блокировки может быть привязан к нескольким условиям. 2. Условие может поддерживать настройки прерывания ответа и тайм-аута.
Глубокий смысл связывания нескольких условий заключается в том, что потоками можно управлять в соответствии с различными ситуациями. Так называемый «более тонкий» контроль означает, что потоки, заблокированные из-за разных условий, находятся в разных очередях условий и будут пробуждаться только тогда, когда их нужно пробудить. Попадая в очередь синхронизации для конкуренции, сам дифференцируется и изолируется.
понимать
Отличие от общей конкуренции блокировок заключается в том, что условие подчеркивает условия. В многопоточной среде условия должны быть выполнены, прежде чем можно будет выполнить определенные операции, в то время как общая конкуренция блокировок просто эквивалентна конкуренции за право выполнить определенный фрагмент кода; .
Обычная модель программирования: Получить замок; while (условное состояние не выполнено) { разблокировать замок; Поток зависает и ждет, пока не будет выполнено условие для уведомления; Восстановите замок; } Операции критического раздела; разблокировать замок;
Троичные отношения: блокировка, ожидание, условное утверждение (предикат). Каждый вызов ожидания неявно связан с определенным условным утверждением; при вызове ожидания определенного условного утверждения вызывающая сторона уже должна удерживать блокировку условной очереди, связанной с этим утверждением, и эта блокировка должна защищать условия, составляющие условное утверждение. Переменные состояния
предикат: функция, которая возвращает тип bool
создавать
Созданный с помощью метода newCondition интерфейса Lock, класс, который необходимо реализовать, реализует свою собственную логику (по сути, создавая экземпляр ConditionObject).
основной метод
пустота ожидания()
Позвольте потоку дождаться, пока он не будет разбужен сигналом или прерыванием; при вызове блокировка, связанная с условием, будет автоматически снята, и поток прекратит выполнение текущей задачи до тех пор, пока он не будет разбужен сигналом/сигналомAll/прерыванием/ложным пробуждением. Потоку необходимо повторить попытку получения блокировки, связанной с условием, прежде чем вызывать метод ожидания.
логическое ожидание (долгое время, единица измерения времени)
Как и в случае с awaitNanos, вы можете указать единицу времени и вернуть false, когда время истечет, в противном случае вернуть true.
long awaitNanos (длинный nanosTimeout)
Аналогично ожиданию, но просыпается не позднее указанного времени и возвращает неиспользованное время.
void awaitUninterruptible();
Аналогично await, но не отвечает на прерывания.
логическое значение awaitUntil(Дата крайнего срока)
Как и в случае с awaitNanos, способ указания времени отличается. Если крайний срок достигнут, он возвращает false, в противном случае — true.
недействительный сигнал()
Разбудите поток в состоянии ожидания. После пробуждения потока ему необходимо снова получить блокировку.
недействительный сигналВсе()
Разбудите все ожидающие потоки
Замок
Введение
Интерфейс предоставляет некоторые общие методы для запроса/снятия блокировок.
Сравнение с синхронизированным
Соответствующие механизмы синхронизации (такие как объекты монитора и т. д.) являются встроенными и недоступны на уровне языка. Блокировка — это альтернативное решение на уровне языка, поэтому вы можете получить некоторые удобства на уровне языка (например, знание). успешно ли получена блокировка и т. д.)
Процесс блокировки и разблокировки Synchronized завершается самим ключевым словом, тогда как Lock необходимо явно вызывать метод блокировки и разблокировки.
Блокировка может использовать переменные условия для более гибкого использования блокировок.
Блокировка может реагировать на прерывания, а синхронизация — нет.
основной метод
недействительная блокировка()
void lockInterruptible()
На прерывания будут реагировать во время процесса запроса блокировки.
логическое значение tryLock()
Попробуйте получить блокировку и вернуть результат напрямую (без блокировки текущего потока).
логическое значение tryLock(долгое время, единица измерения TimeUnit)
tryLock с таймаутом и прерыванием ответа
недействительная разблокировка();
Условие новоеУсловие()
Возвращает объект Condition, связанный с блокировкой
Поддержка блокировки
Введение
Базовые примитивы блокировки потоков (связанные с разрешениями), используемые для создания блокировок и других классов синхронизации.
Нижний уровень основан на реализации Unsafe.
Метод строительства
Частное, строительство объектов данного класса не допускается.
Переменные-члены
частный статический окончательный sun.misc.Unsafe UNSAFE
Встроить статический блок кода
sun.misc.Небезопасно
частный статический окончательный длинный parkBlockerOffset
Получите позицию смещения атрибута parkBlocker в объекте класса Thread через Unsafe.
parkBlocker — это запись, когда поток заблокирован, что позволяет средствам мониторинга и диагностики определить причину блокировки потока. Если поток не заблокирован, он имеет значение null;
частный статический финальный длинный SEED
Получите позицию смещения свойства threadLocalRandomSeed в классе Thread через Unsafe.
частный статический окончательный длинный PROBE
Получите позицию смещения свойства threadLocalRandomProbe в классе Thread через Unsafe.
частный статический окончательный длинный ВТОРИЧНЫЙ
Получите позицию смещения свойства threadLocalRandomSecondarySeed в классе Thread через Unsafe.
Эти три свойства устанавливаются ThreadLocalRandom.
основной метод
общедоступный статический объект getBlocker (Thread t)
Получите объект parkBlocker в объекте Thread через Unsafe.
Private static void setBlocker (Thread t, Object arg)
Частный метод, установите объект parkBlocker в Thread через Unsafe, следующие методы установки блокировщика вызывают этот метод
общественный статический пустотный парк()
Заблокируйте поток и продолжите выполнение после ожидания «разрешения».
Конкретная реализация (Hotspot): класс Parker
具体的实现与平台相关,每个线程都有一个Parker实例,在linux实现中实际上是以mutex和condition最终实现了锁和阻塞的过程(具体看笔记文章,这里不写了)
主要成员变量(linux实现)
private volatile int _counter
表示许可,大于0表示已获取许可,每次park只会将其设置为1,不会递增
Таким образом, вызов unpark дважды, а затем дважды вызов park все равно приведет к блокировке во второй раз.
protected pthread_mutex_t _mutex[1]
互斥变量
在代码中可以通过linux原子指令pthread_mutex_lock、pthread_mutex_trylock、pthread_mutex_unlock对变量进行加锁和解锁操作
protected pthread_cond_t _cond[1]
条件变量
利用线程间共享的全局变量进行同步的一种机制,一般和互斥锁结合使用(避免条件变量的竞争);可以通过pthread_cond_wait、pthread_cond_timedwait等指令进行操作
В ядре Linux также имеется очередь ожидания для управления заблокированными процессами.
方法
park
unpark
Отличия в использовании от ожидания
ожиданию необходимо дождаться уведомления, прежде чем он сможет проснуться. Существует последовательный процесс, и ожидающая парковка может быть вызвана перед парком. В это время программа после парковки может продолжить выполнение.
ожидание требует получения монитора объекта, но парковка не дает
Уведомление
Если при парковке произойдет прерывание, Park также вернется, но не выдаст исключение прерывания.
общественный статический пустотный парк (блокировщик объектов)
Из-за роли parkBlocker более рекомендуется использовать метод park с параметрами блокировщика. После его использования вы можете увидеть подсказку, на каком объекте блокируется поток, с помощью инструментов диагностики, таких как jstack, например, парковка для ожидания. <0x000000045ed12298> (полное имя объекта xxxx)
public static void parkNanos (Блокировщик объектов, длинные наносы)
общественная статическая пустота parkNanos (длинные наносы)
Максимальное время блокировки, прежде чем программа продолжит выполнение
public static void parkUntil (Блокировщик объектов, длительный срок)
public static void parkUntil(длительный срок)
public static void unpark (тема)