Благодаря анализу предыдущих трех статей, мы глубоко понимаем внутреннюю структуру и некоторые концепции дизайна AbstractQueuedSynchronizer, и мы знаем, что AbstractQueuedSynchronichorizer поддерживает состояние синхронизации и две области очереди, которые являются синхронными очередями и условными очередями соответственно. Давайте использовать общественные туалеты в качестве аналогии. Очередь синхронизации является основной зоной очередей. Если общественные туалеты не открыты, каждый, кто хочет войти в туалет, должен здесь стоять в очереди. Очередь состояния в основном установлена для ожидания состояния. Давайте представим, что если человек, наконец, успешно получает замок и входит в туалет через очередь, но обнаружит, что он не приносит туалетную бумагу до удобства. Хотя он беспомощен при столкновении с этой ситуацией, она также должна принять этот факт. В настоящее время он должен сначала выйти и подготовить туалетную бумагу (введите очередь состояния, чтобы подождать). Конечно, прежде чем выходить на улицу, замок должен быть выпущен, чтобы другие могли войти. После подготовки туалетной бумаги (условия выполняются), он должен снова вернуться в синхронную очередь в очередь. Конечно, не все люди, входящие в комнату, не принесли туалетную бумагу. Могут быть и другие причины, по которым они должны сначала прервать операцию и в очереди в очереди. Следовательно, может быть несколько очередей состояния, и разные очереди состояния устанавливаются в соответствии с различными условиями ожидания. Очередь состояния представляет собой односторонний связанный список. Интерфейс условий определяет все операции в очереди состояния. Класс ConditionObject внутри AbstractQueuedSynchronizer реализует интерфейс Condition. Давайте посмотрим, какие операции определяются интерфейсом условия.
Условие публичного интерфейса {// ожидание ответа на прерывание потока void wait () бросает прерывание // ожидание, чтобы не отвечать на прерывание потока void aaitUninterbultable (); // Установка условия, ожидающего относительного времени (без вращения), длинное, аватнанос (длинный наностимет) бросает прерывания; // Установка условия ожидания относительного времени (спин) логическое ожидание (долгое время, единиц времени), бросает прерывания; // Установка условия ожидания абсолютного времени Boolean AwaitTunil (срок даты) бросает прерывание; // разбудить сигнал void -узла головного узла () в очереди состояния; // Просыпать все узлы условия очереди void Signalall (); }Хотя интерфейс условий определяет так много методов, он разделен на две категории. Метод, который начинается с ожидания, - это метод, который поток входит в очередь и ожидает условия, и метод, который начинается с сигнала, является методом, который «разбудит» поток в очереди состояния. Здесь следует отметить, что вызов метода сигнала может или не может разбудить поток. Когда поток будет пробужден, зависит от ситуации, как будет обсуждаться позже, но вызов метода сигнала определенно переместит поток из условной очереди в хвост очереди синхронизации. Для удобства повествования мы пока не будем беспокоиться об этом. Мы будем называть метод сигнала операцией потока условной очередей. Обратите внимание, что существует 5 типов методов ожидания, а именно, ожидание прерывания потока ответа, ожидание прерывания потока без ответа, относительное время невинового ожидания, относительное время вращения времени и абсолютное ожидание времени; Существует только два типа методов сигнала, а именно: работа только пробуждения узла головки очереди и пробуждения всех узлов очереди состояния. Тот же тип методов в основном одинаковы. Из -за ограничений пространства невозможно и не обязательно тщательно говорить об этих методах. Нам нужно только понять один репрезентативный метод, а затем посмотреть на другие методы, чтобы понять их. Поэтому в этой статье я подробно расскажу о методе ожидания и методе сигнала. Другие методы не будут обсуждаться подробно, но будут публиковать исходный код для вашей ссылки.
1. Подождите, когда отвечает на условие прерывания потока
// ожидание в ответ на условие прерывания потока Public Final void wait () бросает прерывание {// Если поток прерывается, исключение выбрасывается, если (Thread.Erenterted ()) {Throw New OffrentException (); } // Добавить текущий поток в хвост узла узла очереди состояния = addConditionWaiter (); // Полный выпуск блокировки перед входом в условие подождите int savedState = Full -Release (Node); int enterruptMode = 0; // Поток был условно ждал в цикле while while while (! Isonsyncqueue (node)) {// Поток, который условно ожидает здесь, есть несколько случаев, когда поток пробуждается: // 1. Передний узел очереди синхронизации был отменен // 2. Установите состояние прямого узла очереди синхронизации, чтобы сигнал не удался // 3. Текущий узел пробуждается после того, как передний узел выпускает блокировку. // текущий поток немедленно проверяет, прерывается ли он. Если это так, это означает, что узел отменяет условие ожидания. В настоящее время узел должен быть выброшен из очереди состояния if ((EnterruptMode = CheckIntertruptWhileWaiting (Node))! = 0) {Break; }} // После того, как поток разбудит, он приобретет блокировку в исключительном режиме if (accurequed (node, savedState) && enterruptMode! = Throw_ie) {enterruptMode = Reinterrupt; } // Эта операция предназначена в основном для предотвращения прерывания потоков перед сигналом, что не приводит к отключению от очереди состояния if (node.nextwaiter! = Null) {unlinkCancelledWaitters (); } // Обработка прерывания, которая отвечает на режим прерывания if (EnterruptMode! = 0) {reportIntertafterWait (EnterruptMode); }}Когда поток вызывает метод ожидания, текущий поток будет обернут в виде узла узла и помещен в хвост очереди. В методе AddConditionWaiter, если обнаружено, что конечный узел очереди в очереди будет отменен, будет вызван метод UnlinkCancelledWaitters для очистки всех отмененных узлов в очереди. Этот шаг является подготовкой для вставки узлов. После обеспечения того, чтобы статус хвостового узла также был условием, будет создан новый узел, чтобы обернуть текущий поток и положить его в хвост очереди состояния. Обратите внимание, что этот процесс просто добавляет узлы в хвост очереди синхронизации без приостановки потоков.
Шаг 2: полностью отпустите блокировку
// Полный выпуск блокировки Final int Fullerease (Node Node) {boolean Faile = true; try {// Получить текущее состояние синхронизации int savedState = getState (); // Использование текущего состояния синхронизации для освобождения блокировки if (release (savedState)) {faill = false; // Если блокировка выпускается успешно, вернуть SavedState; } else {// Если заблокирован выпущен сбой, бросьте исключение времени выполнения. }} наконец {// Убедитесь, что узел установлен в состояние отмены, если (не удалось) {node.waitstatus = node.cancelled; }}}После обертывания текущего потока в узел и добавления его в хвост очередного очереди, метод полного восстановления вызывается для освобождения блокировки. Обратите внимание, что метод с именем Full -Release используется для полного освобождения блокировки, поскольку блокировка повторно, поэтому вам необходимо отпустить блокировку перед условным ожиданием, иначе другие не смогут приобрести блокировку. Если выпущен сбой блокировки, будет брошено исключение времени выполнения. Если блокировка будет успешно выпущена, он вернется к предыдущему состоянию синхронизации.
Шаг 3: Сделайте условия ждать
// Поток ждал в цикле while while while (! Isonsyncqueue (node)) {// потоки, которые ждут условия, приостановлены здесь. Есть несколько случаев, когда поток пробуждается: // 1. Передний узел очереди синхронизации был отменен // 2. Установите состояние прямого узла очереди синхронизации, чтобы сигнал не удался // 3. Текущий узел пробуждается после того, как передний узел выпускает блокировку. Locksupport.park (это); // Текущий поток разбудит немедленно, чтобы проверить, прерывается ли он. Если это так, это означает, что узел отменяет условие ожидания. В настоящее время узел должен быть выброшен из очереди состояния if ((EnterruptMode = CheckIntertruptWhileWaiting (Node))! = 0) {Break; }} // Проверьте ситуацию прерывания потока, когда условие ожидает частное int int urstruptwhilewaiting (узлы узла) {// Запрос прерывания проходит до операции сигнала: throw_ie // Запрос прерывания проходит после операции сигнала: revinterrupt // Запрос на прерывание не было получено в течение этого периода: 0 return stude.rewrupted ()? (TransferAfterCancelledWait (узел)? throw_ie: reventRept): 0;} // Передача узела, который отменяет условие, ожидающее из очереди условия в очередь синхронизации. Node.condition, 0)) {// После успешной модификации статуса поместите узел в хвост очереди синхронизации enq (node); вернуть истину; } // Это указывает на то, что операция CAS не удалась, указывая на то, что прерывание происходит после метода сигнала while (! Isonsyncqueue (node)) {// Если синальный метод не передал узел в очередь синхронизации, подождите Thread.yield (); } вернуть false;}После завершения двух вышеупомянутых операций он войдет в петлю while. Вы можете видеть, что while Loop первые вызовы LockSupport.Park (это) повесить поток, поэтому поток будет заблокирован здесь все время. После вызова метода сигнала просто перенесите узел из условной очереди в очередь синхронизации. Будет ли поток пробужден, зависит от ситуации. Если вы обнаружите, что прямой узел в очереди синхронизации отменен при передаче узла, или состояние прямого узла обновляется до сбыта сигнала, оба случая немедленно разбудит поток. В противном случае поток, который уже находится в очереди синхронизации, не будет пробуждена в конце метода сигнала, но подожду, пока его передний узел не разбудет. Конечно, в дополнение к вызову метода сигнала, чтобы проснуться, поток также может отвечать на прерывания. Если поток получает здесь запрос прерывания, он продолжит выполнять. Вы можете видеть, что после просыпания потока он сразу же проверит, пробуждается ли он прерыванием или методом сигнала. Если он пробуждается прерыванием, он также перенесет этот узел в очередь синхронизации, но он достигается путем вызова метода TransferAfterCancelledWait. После окончательного выполнения этого шага прерывание будет возвращено, а петля будет выскочена.
Шаг 4: Работа после удаления узла из очереди условий
// После того, как поток разбудит, он приобретет блокировку в исключительном режиме if (acpirequed (node, savedState) && enterruptMode! = Throw_ie) {EnterruptMode = Reinterrupt;} // Эта операция в основном для предотвращения прерывания потока перед сигналом и не вызывая контакта с условием, если (node)! UnlinkCancelledWaiters ();} // Обработка прерываний, которая отвечает на режим прерывания if (EnterruptMode! = 0) {reportIntertafterWait (EnterruptMode);} // После окончания ожидания условия, он сделает соответствующую обработку на основе перерыва {////я. брошен if (enterruptMode == throw_ie) {throw new urreptedException (); // Если режим прерывания повторно, он будет вешать сам} else if (EnterruptMode == Reinterrupt) {SelfUnterrupt (); }}Когда поток заканчивает цикл while, то есть условие ожидает, он вернется в очередь синхронизации. Будь то из -за вызова сигнального метода назад или из -за прерывания потока, узел в конечном итоге будет в синхронной очереди. В настоящее время метод приобретения будет вызван для выполнения операции приобретения замков в очереди синхронизации. Мы уже подробно обсудили этот метод в статье эксклюзивного режима. Другими словами, после того, как узел выходит из очереди состояния, он послушно переходит к набору замков в исключительном режиме. После того, как этот узел снова приобретет блокировку, он позвонит в метод отчета Interrustafterwait, чтобы ответить соответствующим образом в зависимости от ситуации прерывания в течение этого периода. Если прерывание происходит до метода сигнала, EntruptMode - это throw_ie, и исключение будет брошено после получения блокировки; Если прерывание возникает после метода сигнала, прерывание прерывания повторно, и оно будет прервано снова после того, как блокировка снова будет получена.
2. В ожидании отсутствия ответов на прерывания потока
// ожидание публичного окончательного void aaitUninterbultable () {// Добавить текущий поток в хвост узла узла состояния = addConditionWaiter (); // Полный выпуск блокировки и вернуть текущее состояние синхронизации int savedState = Full -Release (Node); Boolean прерван = false; // узлы условно ждут в цикле while while (! Isonsyncqueue (node)) {// Все потоки в очереди состояния приостановлены здесь locksupport.park (this); // Поток просыпается и обнаруживает, что прерывание не будет отвечать немедленно, если (Think.Erenptorted ()) {прерван = true; }} if (accirequeed (node, savedState) || прерван) {// отвечать на все запросы прерывания здесь, если одно из следующих двух условий будет выполнено, он будет зависеть // 1. Поток получает запрос прерывания, пока условие ожидает // 2. Поток получает запрос на прерывание в методе Accipequequeed SelfEntrupt (); }}3. Установите относительное условие времени ожидания (без вращения)
// Установите условие времени ожидания (относительное время) и не выполняйте Spin ждать публичного окончательного долгого длинного Awaitnanos (long nanostimeout) броски прерванного перерыва {// Если поток прерывается, исключение бросается, если (Thread.Eruprupted ()) {Throut new InterruptException (); } // Добавить текущий поток в хвост узла узла очереди состояния = addConditionWaiter (); // Полный выпуск блокировки перед входом в условие ожидает int savedState = Full -Release (Node); долго прошлый время = System.nanotime (); int enterruptMode = 0; while (! isonsyncqueue (node)) {// Судите, используется ли тайм -аут, если (nanostimeout <= 0l) {// Если тайм -аут был завершен, вам необходимо выполнить операцию «Операция ожидания отмены» (Node); перерыв; } // Повесить текущий поток в течение определенного периода времени, поток может быть пробужден в течение этого периода, или он может разбудить саму саму Locksupport.parknanos (это, nanostimeout); // Сначала проверяйте информацию о прерывании после того, как поток разбудит if ((EnterruptMode = CheckInterruptWhileWaiting (Node))! = 0) {Break; } long Now = System.nanotime (); // Время тайм -аута минус время ожидания условия nanostimeout - = сейчас - прошлое; прошлое = сейчас; } // После того, как поток разбудит, он приобретет блокировку в эксклюзивном режиме, если (Accipequeed (node, saveState) && enterruptMode! = Throw_ie) {EnterruptMode = Reinterrupt; } // Поскольку метод TransferAfterCancelledWait не опустошен NextWaiter, все, что вам нужно очистить здесь, если (node.nextwaiter! = Null) {unlinkCancelledwaiters (); } // Обработка прерывания, которая отвечает на режим прерывания if (EnterruptMode! = 0) {reportIntertafterWait (EnterruptMode); } // возвращать оставшееся время возврата nanostimeout - (system.nanotime () - прошлый время);}4. Установите относительное условие времени ожидания (спин)
// Установить временное ожидание условия (относительное время), выполнить Spin Waiting Public Final Boolean watiate (долгое время, время TimeUnit) бросает прерванные } // Получить миллисекунды временного времени long nanostimeout = unit.tonanos (время); // Если поток прерывается, исключение бросается, если (Thread.Erenprupted ()) {Throw new прерванного ExtruptException (); } // Добавить текущий поток в хвост узла узла очереди состояния = addConditionWaiter (); // Полный выпуск блокировки перед входом в условие, чтобы подождать int savedState = Full -Release (Node); // Получить миллисекунды текущего времени в прошлое = system.nanotime (); логический временной интернет = false; int enterruptMode = 0; while (! isonsyncqueue (node)) {// Если тайм -аут временен, вам необходимо выполнить операцию ожидания отмены, если (nanostimeout <= 0l) {TimeDout = TransferAfterCancelledWait (node); перерыв; } // Если время ожидания превышает время вращения, поток будет приостановлен в течение определенного периода времени, если (nanostimeout> = spinfortimeoutThreshold) {locksupport.parknanos (this, nanostimeout); } // После того, как поток просыпается, проверяет информацию о прерывании сначала if ((EnterruptMode = CheckIntertwhileWaiting (Node))! = 0) {break; } long Now = System.nanotime (); // Время тайм -аута каждый раз вычитает время состояния, ожидающего nanostimeout - = сейчас - прошлое; прошлое = сейчас; } // После того, как поток разбудит, он приобретет блокировку в эксклюзивном режиме, если (Accipequeed (node, saveState) && enterruptMode! = Throw_ie) {EnterruptMode = Reinterrupt; } // Поскольку метод TransferAfterCancelledWait не опустошает NextWaiter, все, что вам нужно очистить здесь, если (node.nextwaiter! = Null) {unlinkCancelledWaiters (); } // Обработка прерывания, которая отвечает на режим прерывания if (EnterruptMode! = 0) {reportIntertafterWait (EnterruptMode); } // возвращает, возвращает ли флаг тайм -аута! TimeDout;}5. Установите абсолютное время ожидания
// Установить временное условие ожидание (абсолютное время) Общедоступный окончательный логический логический awaitTil (дата срока) бросает прерванные эктриэкцепции {if (deadline == null) {бросить новый NullPointerException (); } // Получить миллисекунды абсолютного времени afstime = deadline.gettime (); // Если поток прерывается, исключение бросается, если (Thread.Erenprupted ()) {Throw new прерванного ExtruptException (); } // Добавить текущий поток в хвост узла узла очереди состояния = addConditionWaiter (); // Полный выпуск блокировки перед входом в условие подождите int savedState = Full -Release (Node); логический временной интернет = false; int enterruptMode = 0; while (! isonsyncqueue (node)) {// Если тайм -аут, вам необходимо выполнить операцию ожидания отмены, если (System.currentTimeMillis ()> Afstime) {TimeDout = TransferAfterCancelledWait (node); перерыв; } // Повесить нить в течение определенного периода времени, в течение которого нить может быть пробуждена, или может прийти время разбудить саму LockSupport.parkuntil (это, Afstime); // Сначала проверяйте информацию о прерывании после того, как поток разбудит ((EnterruptMode = CheckInterruptWhileWaiting (Node))! = 0) {Break; }} // После того, как поток разбудит, он приобретет блокировку в исключительном режиме if (accurequed (node, savedState) && enterruptMode! = Throw_ie) {enterruptMode = Reinterrupt; } // Поскольку метод TransferAfterCancelledWait не опустошен NextWaiter, все, что вам нужно очистить здесь, если (node.nextwaiter! = Null) {unlinkCancelledwaiters (); } // Обработка прерывания, которая отвечает на режим прерывания if (EnterruptMode! = 0) {reportIntertafterWait (EnterruptMode); } // возвращает, возвращает ли флаг тайм -аута! TimeDout;}6. Разбудите узел головного узла в условной очереди
// Просыпайте следующий узел в очереди в очереди public final void Signal () {// Судья, сохраняет ли текущий поток блокировку if (! IsheldexClusively ()) {бросить новый allosalmonitorStateException (); } Узел First = FirstWaiter; // Если есть очередь в очереди на условие if (First! = Null) {// Разбудить узел головного узла в дозигнале в очереди состояния (первое); }} // Разбудить узел головного узла в очереди условий private void dosignal (узел сначала) {do {// 1. Переместите ссылку на первое wataiter один за другим if ((firstwaiter = first.nextwaiter) == null) {lastwaiter = null; } // 2. Опустошить ссылку на узел преемника головного узла First.nextwaiter = null; // 3. Перенесите узел головного узла в очередь синхронизации, и можно разбудить поток после завершения передачи // 4. Если операция TransferForsignal не выполняется, разбудите следующий узел} while (! TransferForsignal (First) && (First = FirstWaiter)! = NULL);} // Передача указанного узла из очереди условий в очередь синхронизации Final Boolean TransperForsignal (Node Node) {// Установить состояние ожидания из условия на 0 IF (! Node.condition, 0)) {// Если операция по обновлению состояния не удается, вернуть false напрямую // может случиться так, что метод TransferafterCancelledWait в первую очередь изменил состояние, в результате чего эта операция CAS не смог вернуть false; } // Добавить этот узел в хвост узла очереди синхронизации p = enq (node); int ws = p.waitstatus; if (ws> 0 ||! CompareandSetwaitStatus (p, ws, node.signal)) {// текущий поток будет пробужден, когда возникнет следующая ситуация // 1. Передний узел находится в состоянии отмены // 2. Состояние обновления прямого узела - это операция сигнала Fake LockSupport.unpark (node.thread); } вернуть true;}Можно видеть, что конечным ядром метода сигнала является вызов метода TransferForsignal. В методе TransferForsignal сначала используйте операцию CAS, чтобы установить состояние узла из условия на 0, а затем вызовите метод ENQ, чтобы добавить узел в хвост очереди синхронизации. Мы видим следующее, если утверждение о суде. Это утверждение суждения в основном используется для определения того, когда поток будет пробужден. Если эти две ситуации произойдут, нить будет немедленно пробуждена. Одним из них обнаружено, что состояние предыдущего узла отменено, а другое - когда состояние предыдущего узла не обновляется. Оба случая немедленно разбудит поток, в противном случае это будет сделано путем простого передачи узла из условной очереди в очередь синхронизации, и не сразу разбудет поток в узле. Метод сигнала примерно похож, за исключением того, что он проходит через все узлы в условной очереди и передает их в синхронную очередь. Метод передачи узлов по -прежнему вызывает метод TransferForsignal.
7. Разбуди все узлы очереди состояния
// Пробуждение всех узлов за очередью условия } // Получить узлом узла заголовка очереди в очереди first = firstwaiter; if (First! = null) {// Разбудить все узлы условия очереди dosignalall (первое); }} // Разбудить все узлы очереди условий private void dosignalall (узел сначала) {// сначала опустошить ссылки на узл заголовка и хвостового узла LastWaiter = Firstwaiter = null; do {// Получить ссылку на преемник узла First = First.NextWaiter; // опустошить последующую ссылку на узел, который будет перенесен первым. Nextwaiter = null; // Перенос узел из условной очереди в очередь синхронизации переносаforsignal (первое); // укажите ссылку на следующий узел первым = Далее; } while (First! = null);}На этом этапе весь наш анализ исходного кода AbstractQueuedSynchronizer закончился. Я считаю, что благодаря этим четырем анализам каждый может лучше освоить и понять AQ. Эта категория действительно очень важна, потому что это краеугольный камень многих других категорий синхронизации. Из -за ограниченного уровня и способности автора выражения, если нет четких утверждений или неадекватного понимания, пожалуйста, поправьте их во времени, обсудите и учитесь вместе. Вы можете оставить сообщение, чтобы прочитать проблему ниже. Если вам нужен исходный код комментариев AQS, вы также можете связаться с автором, чтобы запросить его.
ПРИМЕЧАНИЕ. Все вышеперечисленные анализы основаны на JDK1.7, и будут различия между различными версиями, читатели должны обратить внимание.
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.