Análise do código -fonte de Countdownlatch - Countdown ()
O artigo anterior falou sobre o princípio do Wait () em Countdownlatch a partir do nível do código -fonte. Este artigo fala sobre Countdown ().
public void Countdown () {// CountdownLatch Sync.Releashared (1);} ↓ public final boolean RELEASHARED (int arg) {// aqs if (TryReleashared (arg)) {DoreLeashared (); retornar true; } return false;} ↓} ↓ olha Boolean protegido TryReLeashared (INT Releases) {//countdownlatch.sync // contagem de decrescentes; sinalize quando a transição para zero para (;;) {int c = getState (); if (c == 0) retorna false; int nextc = c-1; if (comparaandStState (c, nextc)) retorna nextc == 0; }}Através do construtor CountdownLatch end = new Countdownlatch (2); O estado está definido como 2, então C == 2, NextC = 2-1,
Em seguida, defina o estado como 1 na operação CAS a seguir.
O BOOLEAN FINAL protegido ComparaDSetState (int espera, int update) {// Veja abaixo a configuração Intrinsics para suportar esse retorno inseguro.compareandswapint (isto, StateOffset, espera, atualização); }No momento, o NextC não é 0 e retorna falsa. Aguarde até que o método countdown () seja chamado duas vezes, estado == 0, nextc == 0, e retorna true neste momento.
Digite o método DORELEASHARED ().
doreleashared (); ∞ private void doreleashared () { / * * * Verifique se uma versão se propaga, mesmo que haja outro * em andamento adquire /libera. Isso prossegue na maneira usual * de tentar desacitrar a cabeça se precisar do sinal *. Mas, se não o fizer, o status será definido para se propagar para * garantir que, após a liberação, a propagação continua. * Além disso, devemos fazer um loop caso um novo nó seja adicionado * enquanto estivermos fazendo isso. Além disso, diferentemente de outros usos do * UMSKSUCCESSOR, precisamos saber se o CAS para redefinir o status * falha, se assim for, a recodificação. */ for (;;) {nó h = head; if (h! = null && h! = cauda) {int ws = h.waitStatus; if (ws == node.signal) {if (! ComparandSetwaitStatus (h, node.signal, 0)) continue; // loop para verificar os casos desmarcados (h); } else if (ws == 0 &&! ComparaDSetWaitStatus (h, 0, node.propagate)) continue; // loop no falha no cas} if (h == head) // loop se a cabeça mudar de interrupção; }}Recordando o modelo de fila de espera neste momento.
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Neste momento, a cabeça não é nula ou cauda. WaitStatus == Node.Signal, então digite o julgamento se (! ComparandSetWaitStatus (H, Node.Signal, 0)).
if (! ComparandSetwaitStatus (h, node.signal, 0)) ↓ /*** Cas WaitStatus de um nó. */Private estático final boolean ComparaDSetwaitStatus (nó nó, int espera, int update) {return insefa.compareandswapint (nó, waitstatusoffset, espere, atualização);}Esta operação CAS define o estado para 0, o que significa que o WaitStatus na cabeça está 0 no momento. O modelo de fila é o seguinte
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Este método retorna true. Digite UMSKSUCCESSOR (H);
UMPARKSUCCESSOR (H); ↓ Void privado UMPARKSUCESSOR (NODE NODE) { / * * Se o status for negativo (ou seja, possível precisando de sinal), tente * limpar em antecipação da sinalização. Tudo bem se isso * falhar ou se o status for alterado por thread de espera. */ int ws = node.waitStatus; if (ws <0) comparandSetwaitStatus (nó, ws, 0); / * * O thread para UMPARK é mantido no sucessor, que normalmente é * apenas o próximo nó. Mas, se cancelado ou aparentemente nulo, * atravessa a cauda para encontrar o sucessor real * não cancelado. */ Nó s = node.next; if (s == null || s.waitStatus> 0) {s = null; for (nó t = cauda; t! = null && t! = node; t = t.prev) if (t.waitstatus <= 0) s = t; } if (s! = null) LockSupport.Unpark (S.Thread);}S é o nó sucessor da cabeça, ou seja, o nó com o thread atual. s! = null e s.waitStatus == 0, então digite LockSupport.UnPark (S.Thread);
public static void UMPARK (Thread Thread) {if (thread! = null) insefe.unpark (thread); }Isto é, o segmento que desbloqueia bloqueado. O árbitro foi autorizado a apitar!
O princípio da contagemdown () é muito claro.
Sempre que o método Countdown () é executado, o estado é reduzido em 1. Até o estado == 0, os threads bloqueados na fila começam a ser liberados e os threads nos nós subsequentes são liberados de acordo com o estado de Waitstatus no nó antecessor.
Ok, volte à questão do artigo anterior, quando o seguinte loop sairá (o loop no método aguardar)
para (;;) {Final Node P = Node.PredeCessor (); if (p == Head) {int r = TryAcquireshared (arg); if (r> = 0) {setheadandpropagate (nó, r); p.next = null; // ajuda gc falhou = false; retornar; }} if (deveparkafterFailedAcQuire (p, node) && parkandcheckinterrupt ()) lançar new interruptedException ();}Neste momento, estado == 0, então digite o método setheadandpropagate.
setheadandpropagate (nó, r); Mes. Void privado setheadandpropagate (nó nó, int propagate) {nó h = head; // grava a cabeça antiga para o cheque abaixo de sethead (nó); / * * Tente sinalizar o próximo nó na fila se: * A propagação foi indicada pelo chamador, * ou foi registrada (como H.WaitStatus antes * ou depois de sethead) por uma operação anterior * (Nota: Isso usa a verificação de sinalização * Shared, porque * propaga o status pode fazer a transição. * despertadores desnecessários, mas somente quando há várias corridas * adquire/libera, então a maioria precisa de sinais agora ou em breve * de qualquer maneira. */ if (propagate> 0 || h == null || h.waitStatus <0 || (h = cabeça) == null || h.waitStatus <0) {node s = node.next; if (s == null || s.isshared ()) DORELEASHAREDARED (); }} ↓ SETHEAD PRIVADO STETH (nó) {head = node; node.thread = null; node.prev = null;}Este método muda o nó sucessor da cabeça na cabeça. Após esse método, o próximo nó do nó é definido como nulo, e o modelo se torna a seguinte figura
Anterior +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Ou seja, a cauda da cabeça do nó e outras coisas estão definidas como nulas, esperando o GC reciclar. Neste momento, retorne, pule para fora do loop e a fila é limpa.
Aqui está uma demonstração de todo o processo
setheadandpropagate (nó, r); +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- thread = null | <---- nó (cauda) | CurrentThread | +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Nó (cauda) | CurrentThread | + ---------------------------------+ ↓ +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
O núcleo do CountdownLatch é uma fila de encadeamento de bloqueio, que é uma fila construída a partir de uma lista vinculada, que contém thread e waitstatus, onde o WaitStatus descreve o status do thread do nó sucessor.
O estado é uma bandeira muito importante. Ao construir, ele é definido como o valor N correspondente. Se n! = 0, a fila de bloqueio será bloqueada o tempo todo, a menos que o thread seja interrompido.
Cada vez que o método Countdown () é chamado, o estado-1 é usado e o método Await () é usado para adicionar o encadeamento chamando o método à fila de bloqueio até o estado == 0, e o thread não pode ser liberado.
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.