Através da análise dos três artigos anteriores, temos um profundo entendimento da estrutura interna e alguns conceitos de design do abstratoqueedsynchronizer, e sabemos que o abstratoqueedSynchronizer mantém um estado de sincronização e duas áreas de filas, que são filas síncronas e filas condicionais, respectivamente. Vamos usar os banheiros públicos como uma analogia. A fila de sincronização é a principal área da fila. Se os banheiros públicos não estiverem abertos, todos que desejam entrar no banheiro precisam fazer fila aqui. A fila de condição é definida principalmente para condições de espera. Vamos imaginar que, se uma pessoa finalmente obtenha com sucesso a fechadura e entra no banheiro na fila, mas descobrir que não traz papel higiênico antes da conveniência. Embora ele esteja impotente ao encontrar essa situação, ela também deve aceitar esse fato. Neste momento, ele deve sair e preparar o papel higiênico primeiro (insira a fila de condições para esperar). Obviamente, antes de sair, a fechadura deve ser liberada para que outros possam entrar. Depois de preparar o papel higiênico (as condições são atendidas), ela deve retornar à fila síncrona para a fila novamente. Obviamente, nem todas as pessoas que entram na sala não trouxeram papel higiênico. Pode haver outras razões pelas quais eles devem interromper a operação e a fila na fila de condições primeiro. Portanto, pode haver várias filas de condição, e diferentes filas de condições são definidas de acordo com diferentes condições de espera. A fila de condições é uma lista vinculada de mão única. A interface de condição define todas as operações na fila de condições. A classe ConditionObject dentro do abstratoQueedSynchronizer implementa a interface de condição. Vamos dar uma olhada em quais operações são definidas pela interface da condição.
condição de interface pública {// aguardando a resposta à interrupção do thread void aguart () lança interruptedException; // aguardando por não responder à interrupção do thread vazio aguardando (); // Definindo a condição aguardando tempo relatório (sem rotação) aguardarnanos (Nanostimeout) lança interrupção; // Definindo a condição que aguarda o tempo relativo (spin) boolean aguardo (longo tempo, unidade de unidade de tempo) lança interruptedException; // Definindo condição aguardando o tempo absoluto boolean aguardo (prazo de data) lança interruptedException; // Acorde o sinal void do nó da cabeça () na fila da condição; // Acorde todos os nós da fila de condição SignalAllAll (); }Embora a interface da condição defina muitos métodos, ela é dividida em duas categorias no total. O método que começa com aguarda é o método de que o encadeamento insere a fila de condição e espera, e o método que começa com o sinal é o método que "acorda" a rosca na fila de condições. Deve -se notar aqui que chamar o método de sinal pode ou não acordar o thread. Quando o thread for despertado depende da situação, como será discutido posteriormente, mas chamar o método do sinal definitivamente moverá o encadeamento da fila condicional para a cauda da fila de sincronização. Por uma questão de conveniência da narração, não nos preocuparemos com isso por enquanto. Chamaremos o método de sinal de operação do encadeamento da fila condicional de despertar. Observe que existem 5 tipos de métodos aguardando, a saber, o thread de resposta que interrompe a espera, a interrupção do thread que não response espera, o tempo relativo que não está esperando, a rotação relativa do tempo aguardando e o tempo absoluto que espera; Existem apenas dois tipos de métodos de sinal, a saber, a operação de despertar apenas o nó da cabeça da fila de condição e despertar todos os nós da fila de condições. O mesmo tipo de método é basicamente o mesmo. Devido a limitações de espaço, é impossível e não é necessário falar sobre esses métodos cuidadosamente. Precisamos apenas entender um método representativo e depois procurar outros métodos para entendê -los. Portanto, neste artigo, falarei apenas sobre o método aguardar e o método de sinal em detalhes. Outros métodos não serão discutidos em detalhes, mas publicarão código -fonte para sua referência.
1. Aguarde a resposta à condição de interrupção do thread
// aguardando em resposta à condição do thread interrompe o vazio final público aguardar () lança interruptedException {// Se o thread for interrompido, uma exceção será lançada se (thread.interrupted ()) {lança new interrupteDexception (); } // Adicione o encadeamento atual à cauda da condição Nó da fila do nó = addConditionWaiter (); // Liberação completa do bloqueio antes de inserir a condição Wait int SavedState = totalmente lançamento (nó); int interruptMode = 0; // O thread está aguardando condicionalmente o loop while while (! IsonsyncQueue (nó)) {// O thread que está esperando condicionalmente é suspenso aqui, há vários casos em que o thread é despertado: // 1. O nó avançado da fila de sincronização foi cancelado // 2. Defina o estado do nó direto da fila de sincronização para sinalizar falhando // 3. O nó atual é despertado depois que o nó avançado libera o bloqueio. // O thread atual verifica imediatamente se é interrompido. Nesse caso, significa que o nó cancela a condição que espera. Neste momento, o nó precisa ser movido da fila de condições if ((interruptMode = checkInterruptwhilewaiting (nó))! = 0) {break; }} // Depois que o thread acordar, ele adquirirá o bloqueio no modo exclusivo se (adquirido (nó, SavedState) && interruptMode! = Throw_ie) {interruptMode = reinterrupt; } // Esta operação é principalmente para impedir que os threads interrompem antes do sinal, resultando em nenhuma desconexão da fila de condições se (node.nextwaiter! = Null) {UnbinkCancelledwaiters (); } // interrompe o processamento que responde ao modo de interrupção se (interruptMode! = 0) {reportInterruptAfterWait (interruptMode); }}Quando um encadeamento chama o método aguardando, o encadeamento atual será envolvido como um nó e colocado na cauda da fila de condições. No método AddConditionwaiter, se o nó final da fila de condição for cancelado, o método UnbinkCancelledwaiters será chamado para limpar todos os nós cancelados da fila de condições. Esta etapa é a preparação para a inserção de nós. Depois de garantir que o status do nó da cauda também seja uma condição, um novo nó será criado para embrulhar o fio atual e colocá -lo na cauda da fila de condições. Observe que esse processo apenas adiciona nós à cauda da fila de sincronização sem suspender threads.
Etapa 2: solte completamente a trava
// Liberação completa O bloqueio final int totalmente fundamental (nó nó) {boolean falhou = true; tente {// obtenha o estado de sincronização atual int savedState = getState (); // Use o estado de sincronização atual para liberar o bloqueio if (release (SAVEDSTATE)) {falhou = false; // Se o bloqueio for lançado com sucesso, retorne o SavedState; } else {// Se o bloqueio for liberado falhar, jogue uma exceção de tempo de execução, jogue novo ilegalMonitorStateException (); }} finalmente {// verifique se o nó está definido como o estado de cancelamento se (falhou) {node.waitStatus = node.cancelled; }}}Depois de embrulhar o encadeamento atual em um nó e adicioná -lo à cauda da fila de condições, o método de liberação totalmente chamado para liberar a trava. Observe que o método denominado Rellease é usado para liberar completamente o bloqueio, porque o bloqueio é reentrante; portanto, você precisa liberar o bloqueio antes da espera condicional, caso contrário, outros não poderão adquirir o bloqueio. Se o bloqueio for liberado falhar, uma exceção de tempo de execução será lançada. Se o bloqueio for lançado com sucesso, ele retornará ao estado de sincronização anterior.
Etapa 3: Faça as condições esperando
// O thread está esperando no while loop while (! Isonsyncqueue (nó)) {// Os threads que estão aguardando a condição são suspensos aqui. Existem vários casos em que o thread é despertado: // 1. O nó avançado da fila de sincronização foi cancelado // 2. Defina o estado do nó direto da fila de sincronização para sinalizar falhando // 3. O nó atual é despertado depois que o nó avançado libera o bloqueio. Locksupport.park (isto); // O encadeamento atual acorda imediatamente para verificar se é interrompido. Nesse caso, significa que o nó cancela a condição que espera. Neste momento, o nó precisa ser movido da fila de condições if ((interruptMode = checkInterruptwhilewaiting (nó))! = 0) {break; }} // Verifique a situação de interrupção do thread quando a condição de espera privada int checkInterrupthilewaiting (nó nó) {// A solicitação de interrupção é antes da operação do sinal: throw_ie // a solicitação de interrupção é após a operação do sinal: reinterrutar // Nenhuma solicitação de interrupção foi recebida durante esse período: 0 Retorno Thread.Interrubed ()? (TransferAfterCancelledWait (nó)? Throw_ie: reinterruptiva): 0;} // Transfira o nó que cancela a condição que espera da fila de condições para a fila de sincronização, se esta operação de Cas (se a operação é bem -sucedida, que significa que a interrupção da interrupção antes da interrupção (nó) (//, se a operação CAS é bem -sucedida, que significa que a ocorrência da interrupção ( Node.condition, 0)) {// Após a modificação do status ser bem -sucedida, coloque o nó na cauda da fila de sincronização ENQ (nó); retornar true; } // Isso indica que a operação do CAS falhou, indicando que a interrupção ocorre após o método do sinal enquanto (! Isonsyncqueue (nó)) {// se o método sinal não transferiu o nó para a fila de sincronização, aguarde o Thread.yield (); } retornar false;}Depois que as duas operações acima forem concluídas, ele entrará no loop while. Você pode ver que o loop while o loop chama de LockSupport.park (isso) para pendurar o thread, para que o thread seja bloqueado aqui o tempo todo. Depois de chamar o método do sinal, basta transferir o nó da fila condicional para a fila de sincronização. Se o tópico será despertado depende da situação. Se você achar que o nó avançado na fila de sincronização é cancelado ao transferir um nó ou o estado do nó para a frente é atualizado para falhar, os dois casos acordarão imediatamente o thread. Caso contrário, o thread que já está na fila de sincronização não será despertado no final do método do sinal, mas aguardará até que seu nó para a frente acorde. Obviamente, além de chamar o método de sinal para acordar, o thread também pode responder às interrupções. Se o thread receber uma solicitação de interrupção aqui, ele continuará sendo executado. Você pode ver que, depois que o thread acordar, ele verificará imediatamente se ele foi despertado por interrupção ou por método de sinal. Se for despertado pela interrupção, também transferirá esse nó para a fila de sincronização, mas é alcançado chamando o método transferafterCancelledwait. Após a execução final desta etapa, a interrupção será devolvida e o loop while será saltado.
Etapa 4: Operação após a remoção do nó da fila de condições
/Após o tópico acordar, ele adquirirá o bloqueio no modo exclusivo se (adquirido (nó, SAVEDSTATE) && interruptMode! = Throw_ie) {interruptMode = reinterrupt;} // Esta operação é principalmente para impedir que o fio seja interrompido antes do sinal e não cause o contato com a condição se (node. UNLINKCANCELLEDWAITERS ();} // interrompe o processamento que responde ao modo de interrupção se (interruptMode! = 0) {reportInterruptAfterWait (interrupção);} // Após o término da condição que se espera, que interrompe o processamento interrompido, com base na situação de interrupção interrompida (Int Intermode), que interrompe o interceptmode, com base na parte interna de interrupção (Int intermode), que interrompe -se (interceptmode), que interrompe, o interceptmode (interceptmode), que interrome, o interceptmode, que interrompe, o interceptMode, que interrompe o interceptmode, o interceptmode, que interrompe o interruptor, o interceptmode (intercel jogado if (interruptMode == throw_ie) {tiro new interruptEdException (); // Se o modo de interrupção for reinterrupto, ele se pendurará} else if (interruptMode == reinterrupt) {selfInterrupt (); }}Quando o thread termina o loop while, ou seja, a condição espera, ele retornará à fila de sincronização. Seja por causa de chamar o método do sinal de volta ou por causa da interrupção do encadeamento, o nó acabará por estar na fila síncrona. Neste momento, o método adquirido será chamado para executar a operação de adquirir bloqueios na fila de sincronização. Já discutimos esse método em detalhes no artigo de modo exclusivo. Em outras palavras, depois que o nó sai da fila de condições, ele obedientemente vai para o conjunto de bloqueios no modo exclusivo. Depois que este nó adquire o bloqueio novamente, ele chamará o método ReportInterruptfterwait para responder de acordo com a situação de interrupção durante esse período. Se a interrupção ocorrer antes do método do sinal, o interruptor será throw_ie e uma exceção será lançada após a obtenção da fechadura; Se a interrupção ocorrer após o método do sinal, o interruptor será reinterrubado e será interrompido novamente após a obtenção do bloqueio novamente.
2. Aguardando a não resposta interromper o encadeamento
// espera em vazio final público AwaitunInterruptly () {// Adicione o encadeamento atual à cauda da condição Nó da fila = AddConditionWaiter (); // liberação completa do bloqueio e retorne o estado de sincronização atual int savedState = totalmente lançamento (nó); interrompido booleano = false; // Os nós estão aguardando condicionalmente o loop while while (! IsonsyncQueue (nó)) {// Todos os threads na fila de condições são suspensos aqui LockSupport.park (this); // O thread acorda e descobre que a interrupção não responderá imediatamente se (thread.interrupted ()) {interrompe = true; }} if (adquirequeed (nó, SavedState) || interrompido) {// responda a todas as solicitações de interrupção aqui, se uma das duas condições a seguir for atendida, ele se pendurará // 1. O thread recebe a solicitação de interrupção enquanto a condição está esperando // 2. O thread recebe a solicitação de interrupção no método adquirido e selfInterrupt (); }}3. Defina a condição de tempo relativa esperando (sem rotação)
// Defina a condição de tempo que espera (tempo relativo) e não execute spin waiting public final Long Waitnanos (Nanostimeout Long) lança interruptedException {// Se o thread for interrompido, uma exceção será lançada se (thread.interrupted ()) {tiro new interruptException (); } // Adicione o encadeamento atual à cauda da condição Nó da fila do nó = addConditionWaiter (); // Liberação completa do bloqueio antes de inserir a condição Waiting int SavedState = totalmente lançamento (nó); Long LastTime = System.Nanotime (); int interruptMode = 0; enquanto (! isonsyncQueue (nó)) {// julga se o tempo limite é usado se (nanostimeout <= 0l) {// se o tempo limite tiver sido concluído, você precisar quebrar; } // pendure o thread atual por um período de tempo, o thread pode ser despertado durante esse período, ou pode acordar por si só Locksupport.parknanos (isto, nanostimeout); // Verifique as informações de interrupção primeiro após o thread acordar if ((interruptMode = checkInterruptWhilewaiting (nó))! = 0) {break; } long agora = System.nanotime (); // Tempo de tempo limite menos o tempo de espera da condição nanostimeout - = agora - última hora; última hora = agora; } // Depois que o thread acorda, ele adquirirá o bloqueio no modo exclusivo se (adquirido (nó, SAVEDSTATE) && interruptMode! = Throw_ie) {interruptMode = reinterrupt; } // porque o método transferafterCancelledwait não esvazia o Nextwaiter, tudo o que você precisa limpar aqui se (node.nextwaiter! = Null) {unninkCancelledwaiters (); } // interrompe o processamento que responde ao modo de interrupção se (interruptMode! = 0) {reportInterruptAfterWait (interruptMode); } // retorna o tempo restante retorna nanostimeout - (system.nanotime () - última hora);}4. Defina a condição de tempo relativa esperando (rotação)
// Defina a condição cronometrada em espera (tempo relativo), execute spin waiting public final boolean aguardo (longo tempo, unidade de unidade de tempo) lança interruptedException {if (unit == null) {lança new nullPointerException (); } // Obtenha os milissegundos do tempo limite nanostimeout = unit.tonanos (tempo); // Se o thread for interrompido, uma exceção será lançada se (thread.interrupted ()) {lança new interruptEdException (); } // Adicione o encadeamento atual à cauda da condição Nó da fila do nó = addConditionWaiter (); // Libere o bloqueio completo antes de inserir a condição para esperar int savedState = totalmente lançamento (nó); // Obtenha os milissegundos do tempo atual Last Time = System.nanotime (); Boolean timedout = false; int interruptMode = 0; while (! isonsyncQueue (nó)) {// Se o tempo limite estiver cronometrado, você precisará executar a operação de espera de condição de cancelamento se (nanostimeout <= 0l) {timedout = transferafterCancelledwait (nó); quebrar; } // Se o tempo de tempo limite for maior que o tempo de rotação, o thread será suspenso por um período de tempo se (nanostimeout> = spinfortimeoutthreshold) {LockSupport.parknanos (this, nanostimeout); } // Depois que o thread acorda, verifique as informações de interrupção primeiro se ((interruptMode = checkInterruptwhilewaiting (nó))! = 0) {break; } long agora = System.nanotime (); // Tempo limite de tempo limite cada vez que subtraia o tempo da condição que espera nanostimeout - = agora - última hora; última hora = agora; } // Depois que o thread acorda, ele adquirirá o bloqueio no modo exclusivo se (adquirido (nó, SAVEDSTATE) && interruptMode! = Throw_ie) {interruptMode = reinterrupt; } // Como o método TransferAfterCancelledWait não esvazia o Nextwaiter, tudo o que você precisa limpar aqui se (node.nextwaiter! = Null) {unninkCancelledwaiters (); } // interrompe o processamento que responde ao modo de interrupção se (interruptMode! = 0) {reportInterruptAfterWait (interruptMode); } // retorna se o sinalizador de tempo limite retorna! Timedout;}5. Defina a condição de tempo absoluta esperando
// Defina a condição cronometrada aguarda (tempo absoluto) public final boolean aguardo (prazo de data) lança interruptedException {if (prazo == null) {lança new nullPointerException (); } // Obtenha os milissegundos do tempo absoluto abstime = prazo.gettime (); // Se o thread for interrompido, uma exceção será lançada se (thread.interrupted ()) {lança new interruptEdException (); } // Adicione o encadeamento atual à cauda da condição Nó da fila do nó = addConditionWaiter (); // Liberação completa do bloqueio antes de inserir a condição Wait int SavedState = totalmente lançamento (nó); Boolean timedout = false; int interruptMode = 0; while (! isonsyncQueue (nó)) {// Se o tempo limite, você precisará executar uma operação de espera de cancelamento se (system.currenttimemillis ()> abstime) {timedout = transferafterCancelledwait (nó); quebrar; } // Pendure o tópico por um período de tempo, durante o qual o thread pode ser despertado, ou pode ser hora de acordar por si só Locksupport.Parkuntil (isto, abstim); // Verifique as informações de interrupção primeiro depois que o thread acorda ((interruptMode = checkInterruptwhilewaiting (nó))! = 0) {break; }} // Depois que o thread acordar, ele adquirirá o bloqueio no modo exclusivo se (adquirido (nó, SavedState) && interruptMode! = Throw_ie) {interruptMode = reinterrupt; } // porque o método transferafterCancelledwait não esvazia o Nextwaiter, tudo o que você precisa limpar aqui se (node.nextwaiter! = Null) {unninkCancelledwaiters (); } // interrompe o processamento que responde ao modo de interrupção se (interruptMode! = 0) {reportInterruptAfterWait (interruptMode); } // retorna se o sinalizador de tempo limite retorna! Timedout;}6. Acorde o nó da cabeça na fila condicional
// Acorde o próximo nó na fila de condição Public Final Void Signal () {// julga se o thread atual mantém o bloqueio if (! IsHelDexclusivamente ()) {lança new IllegalMonitorStateException (); } Nó primeiro = FirstWaiter; // se houver uma fila na fila de condições se (primeiro! = Null) {// acorde o nó da cabeça na fila de condição dosignal (primeiro); }} // Acorde o nó da cabeça na fila de condição private void Dosignal (nó primeiro) {do {// 1. Mova a referência FirstWaiter um por um if ((FirstWaiter = First.Nextwaiter) == null) {lastwaiter = null; } // 2. Esvazie a referência do nó sucessor do nó da cabeça primeiro.Nextwaiter = null; // 3. Transfira o nó da cabeça para a fila de sincronização e é possível acordar o thread após a conclusão da transferência // 4. Se a operação TransferForSignal falhar, acorde o próximo nó} while (! Transferforsignal (primeiro) && (primeiro = FirstWaiter)! = Null);} // transfira o nó especificado da fila de condição para a fila de sincronização, a fila de síncronização, o estado da BOOLEAN REFORMAGEM (Node! Node.condition, 0)) {// Se a operação para atualizar o status falhar, retorne false diretamente // pode ser que o método transferafterCancelledwait mudasse o estado primeiro, fazendo com que essa operação CAS falhe retornar false; } // Adicione este nó à cauda do nó da fila de sincronização P = enq (nó); int ws = p.waitstatus; if (ws> 0 ||! ComparaDSetWaitStatus (p, ws, node.signal)) {// O encadeamento atual será despertado quando a seguinte situação ocorrer // 1. O nó avançado está no estado de cancelamento // 2. O estado do nó de atualização para a operação do sinal falhou LockSupport.unpark (node.thread); } retornar true;}Pode -se observar que o núcleo final do método de sinal é chamar o método transferforsignal. No método TransferForSignal, primeiro use a operação CAS para definir o estado do nó de condição para 0 e depois chamar o método ENQ para adicionar o nó à cauda da fila de sincronização. Vemos a próxima declaração de julgamento. Esta declaração de julgamento é usada principalmente para determinar quando o thread será despertado. Se essas duas situações ocorrerem, o fio será despertado imediatamente. Uma é quando se descobre que o estado do nó anterior é cancelado e o outro é quando o estado do nó anterior falha em atualizar. Ambos os casos acordarão imediatamente o thread, caso contrário, será feito simplesmente transferindo o nó da fila condicional para a fila de sincronização e não acordará imediatamente o thread no nó. O método do SignalAll é aproximadamente semelhante, exceto que ele atravessa todos os nós na fila condicional e os transfere para a fila síncrona. O método de transferência de nós ainda chama o método transferforsignal.
7. Acorde todos os nós da fila de condições
// Acorde todos os nós por trás da fila de condição Public Final Void Signalall () {// julga se o thread atual mantém o bloqueio if (! IsHelDexclusivamente ()) {lança new IllegalMonitorStateException (); } // Obtenha o nó do cabeçalho da fila de condição nó primeiro = FirstWaiter; if (primeiro! = null) {// Acorde todos os nós da fila de condição Dosignalall (primeiro); }} // Acorde todos os nós da fila de condição private vazio Dosignalall (nó primeiro) {// primeiro esvazia as referências do nó do cabeçalho e o nó da cauda lastwaiter = FirstWaiter = null; faça {// obtenha a referência do nó sucessor primeiro = primeiro.nextwaiter; // esvazia a referência subsequente do nó a ser transferida primeiro.nextwaiter = null; // Transfira o nó da fila condicional para a fila de sincronização transferForSignal (primeiro); // apontar a referência ao próximo nó primeiro = a seguir; } while (primeiro! = nulo);}Neste ponto, toda a nossa análise de código -fonte abstrataqueedSynchronizer acabou. Acredito que, através dessas quatro análises, todos podem dominar e entender melhor o AQS. Essa categoria é realmente muito importante porque é a pedra angular de muitas outras categorias de sincronização. Devido ao nível limitado e à capacidade de expressão do autor, se não houver declarações claras ou entendimento inadequado, corrija -os no tempo e discuta e aprenda juntos. Você pode deixar uma mensagem para ler o problema abaixo. Se você precisar do código -fonte do comentário do AQS, também poderá entrar em contato com o autor para solicitá -lo.
Nota: Toda a análise acima é baseada no JDK1.7, e haverá diferenças entre diferentes versões, os leitores precisam prestar atenção.
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.