No artigo anterior "Série de concorrência Java [1] ----- AbstractQueUedSynchronizer Fonte Análise do Código", introduzimos alguns conceitos básicos de abstratoqueedsynchronizer, principalmente falando sobre como a área da fila dos AQs é implementada, qual é o modo exclusivo e o modo compartilhado e sobre como entender o estado dos nós dos nós. Compreender e dominar esses conteúdos é a chave para a leitura subsequente do código -fonte do AQS, por isso é recomendável que os leitores leiam meu artigo anterior primeiro e depois olhe para este artigo para entendê -lo. Neste artigo, introduziremos como os nós entram na fila de sincronização na fila no modo exclusivo e quais operações serão executadas antes de deixar a fila de sincronização. O AQS fornece três maneiras de obter bloqueios no modo exclusivo e no modo compartilhado: aquisição de interrupção de encadeamento não responsiva, aquisição de interrupções de threads de resposta e definir a aquisição do tempo limite. As etapas gerais desses três métodos são aproximadamente as mesmas, com apenas algumas partes diferentes; portanto, se você entender um método e observar a implementação de outros métodos, você será semelhante. Neste artigo, vou me concentrar no método de aquisição de não responder às interrupções do thread, e os outros dois métodos também falarão sobre as inconsistências.
1. Como obter bloqueios com interrupções de encadeamento não responsivas?
// não responde à aquisição do método de interrupção (modo exclusivo) public Final Void adquirir (int arg) {if (! TryAcquire (arg) && adquirequeed (addwaiter (node.exclusive), arg)) {selfInterrupt (); }}Embora o código acima pareça simples, ele executa as 4 etapas mostradas na figura abaixo em ordem. Abaixo, demonstraremos e analisaremos passo a passo.
Etapa 1:! Tryacquire (arg)
// Tente adquirir o bloqueio (modo exclusivo) Boolean TryAcquire (int arg) {tiro novo UnsupportEdOperationException ();}Nesse momento, alguém veio e ele tentou bater na porta primeiro. Se ele descobrisse que a porta não estava trancada (Tryacquire (arg) = true), ele entraria diretamente. Se você achar que a porta está trancada (Tryacquire (arg) = false), execute a próxima etapa. Esse método TryAcquire determina quando a trava está aberta e quando a trava é fechada. Este método deve ser substituído por subclasses e reescrever a lógica do julgamento dentro.
Etapa 2: Addwaiter (Node.exclusive)
// Enrole o encadeamento atual em um nó e adicione -o à cauda da fila de sincronização Addwaiter (modo nó) {// Especifique o modo que segura o nó de bloqueio = novo nó (Thread.currentThread (), modo); // Obtenha a referência do nó da cauda do nó da fila de sincronização pred = cauda; // Se o nó da cauda não estiver vazio, significa que a fila de sincronização já possui um nó se (pred! = Null) {// 1. Aponte para o nó de cauda atual.prev = pred; // 2. Defina o nó atual como o nó da cauda se (comparaDaTettail (Pred, Node)) {// 3. Aponte o sucessor do nó da cauda antiga para o novo nó da cauda Pred.Next = Node; Nó de retorno; }} // Caso contrário, significa que a fila de sincronização não foi inicializada ENQ (nó); Nó de retorno;} // Nó ENQUEUE PRIVADO ENQ (NODE FINAL NODE) {for (;;) {// Obtenha a referência do nó da cauda do nó da fila de sincronização t = Tail; // Se o nó da cauda estiver vazio, significa que a fila de sincronização não foi inicializada se (t == null) {// inicialize a fila de sincronização if (comparaandSthead (new Node ())) {cauda = cabeça; }} else {// 1. Aponte para o nó de cauda atual.prev = t; // 2. Defina o nó atual no nó da cauda se (comparaDaTettail (t, nó)) {// 3. Aponte o sucessor do nó da cauda antiga para o novo nó da cauda t.next = nó; retornar t; }}}}A execução para esta etapa indica que a primeira vez que a aquisição de bloqueio falhará, para que a pessoa receba um cartão numérico e entre na área da fila para fazer fila. Ao receber o cartão numérico, ele declarará como deseja ocupar a sala (modo exclusivo ou modo de compartilhamento). Observe que ele não se sentou e descansou neste momento (espere -se).
Etapa 3: adquirido (addwaiter (node.exclusive), arg)
// adquirindo o bloqueio de uma maneira ininterrupta (modo exclusivo) boolean final adquirido (nó final, int arg) {boolean falhou = true; tente {boolean interromped = false; para (;;) {// obtenha a referência do nó anterior do nó final dado nó p = node.predeCessor (); // Se o nó atual for o primeiro nó da fila de sincronização, tente adquirir o bloqueio se (p == Head && TryAcquire (arg)) {// Defina o nó dado como o nó da cabeça sethead (nó); // Para ajudar a coleta de lixo, limpe o sucessor do nó de cabeça anterior p.next = null; // Defina o estado de aquisição bem -sucedido falhou = false; // retorna o estado interrompido, todo o loop é executado aqui, o retorno de saída interrompido; } // Caso contrário, significa que o status de bloqueio ainda não está disponível. No momento, determine se o thread atual pode ser suspenso // Se o resultado for verdadeiro, o encadeamento atual poderá ser suspenso; caso contrário, o loop continuará, durante esse período, o thread não responderá à interrupção se (deveparkafterFailedacquire (p, nó) && parkandcheckinter ()) {interrupted = true; }}} finalmente {// Certifique -se de cancelar a aquisição se (falhou) {cancelacquire (nó); }}} // julgue se pode suspender o nó atual booleano estático privado deve serparkafterfailedacquire (nó pred, nó nó) {// obtenha o estado de espera do nó avançado int ws = pred.waitstatus; // Se o estado do nó avançado estiver sinal, significa que o nó avançado acordará o nó atual, para que o nó atual possa suspender com segurança se (ws == node.signal) {return true; } if (ws> 0) {// A operação a seguir deve limpar todos os nós de avanço cancelados na fila de sincronização do {node.prev = pred = pred.prev; } while (pred.waitstatus> 0); pred.Next = node; } else {// Para esse fim, significa que o estado do nó para a frente não é sinal e é provável que seja igual a 0. Dessa maneira, o nó para a frente não acordará o nó atual./ também o nó atual deve garantir que o estado do nó para a frente seja o sinal para pendurar em segurança o que compara a comparação (predomeram, n.signo; } retornar false;} // suspender o thread atual Private final boolean parkandcheckinterrupt () {LockSupport.park (this); Retornar Thread.Interrupted ();}Depois de obter o sinal numérico, ele implementará imediatamente esse método. Quando um nó entra na área da fila pela primeira vez, existem duas situações. Uma é que ele descobre que a pessoa à sua frente deixou o assento e entrou na sala, para que não se sente e descanse, e baterá na porta novamente para ver se o garoto está pronto. Se a pessoa lá dentro tivesse terminado, ela se apressaria sem se chamar. Caso contrário, ele teria que considerar sentar e descansar por um tempo, mas ainda estava preocupado. E se ninguém o lembrasse depois que ele se sentou e adormecesse? Ele deixou uma pequena nota no assento do homem na frente, para que a pessoa que saiu de dentro poderia acordá -lo depois de ver a nota. Outra situação é que, quando ele entrou na área da fila e descobriu que havia várias pessoas na frente dele, ele podia sentar por um tempo, mas antes disso, ele ainda deixava uma nota no assento da pessoa na frente (ele já estava dormindo naquele momento) para que a pessoa o acordasse antes de sair. Quando tudo é feito, ele dorme em paz. Observe que vemos que todo o loop tem apenas uma saída, ou seja, ele só pode sair depois que o tópico adquire com sucesso a fechadura. Antes de ser obtida a fechadura, ela é sempre pendurada no método parkandcheckinterrupt () do loop for. Depois que o tópico é despertado, ele também continua a executar o loop for deste local.
Etapa 4: Authinterrupt ()
// O thread atual se interromperá private estático vazio auto -interrupto () {Thread.currentThread (). Interrupt (); }Como todo o tópico acima foi pendurado no método parkandcheckinterrupt () do loop for, ele não responde a nenhuma forma de interrupção do thread antes de ser adquirida com sucesso. Somente quando o thread adquirir com sucesso o bloqueio e sair do loop for, ele verificará se alguém pede para interromper o thread durante esse período. Nesse caso, chame o método selfInterrupt () para pendurar em si.
2. Como obter bloqueios em resposta às interrupções do thread?
// adquirindo o bloqueio no modo interrompível (modo exclusivo) private vazio doacquireinterruptível (int arg) lança interruptedException {// envolvendo o encadeamento atual em um nó e adicionando -o à fila de sincronização nó final nó = addwaiter (node.exclusive); booleano falhou = true; tente {for (;;) {// obtendo o nó anterior nó final p = node.predeCessor (); // Se p é um nó da cabeça, o encadeamento atual tentará adquirir o bloqueio novamente se (p == Head && TryAcquire (arg)) {sethead (nó); p.next = null; // ajuda gc falhou = false; // Retorno de retorno após a admissão com sucesso; } // Se a condição for atendida, o encadeamento atual será suspenso. No momento, uma interrupção é respondida e uma exceção é lançada se (deve (nó) e parkandCheckInterrupt ()) {// Se o thread for despertado, uma exceção será lançada se a solicitação de interrupção for encontrada, uma falha será lançada. lançar new interruptedException (); }}} finalmente {if (falhou) {cancelacquire (nó); }}}O método de interrupção do encadeamento de resposta e o método de interrupção de encadeamento não responsivo são aproximadamente os mesmos no processo de obtenção de bloqueios. A única diferença é que, depois que o thread acorda no método ParkandCheckInterrupt, ele verificará se o thread é interrompido. Nesse caso, ele lançará uma exceção interrompedException. Em vez de responder ao bloqueio de aquisição de interrupção do encadeamento, ele apenas define o estado de interrupção após o recebimento da solicitação de interrupção e não encerrará imediatamente o método atual de aquisição do bloqueio. Ele não decidirá se deve se pendurar com base no estado de interrupção depois que o nó adquire com sucesso o bloqueio.
3. Como definir o tempo de tempo limite para adquirir a fechadura?
// adquirindo o bloqueio com um tempo limitado (modo exclusivo) Private Boolean Doacquirenanos (int arg, Long Nanostimeout) lança interruptEdException {// obtendo o tempo atual LongTime = System.nanotime (); // envolver o encadeamento atual em um nó e adicioná -lo à fila de sincronização Nó final nó = addwaiter (node.exclusive); booleano falhou = true; tente {for (;;) {// obtendo o nó anterior nó final p = node.predeCessor (); // Se o nó anterior for um nó da cabeça, o encadeamento atual tentará adquirir o bloqueio novamente se (p == Head && TryAcquire (arg)) {// Atualize o nó da cabeça sethead (nó); p.next = null; falhou = false; retornar true; } // Depois que o tempo de tempo limite for usado, saia do loop diretamente se (nanostimeout <= 0) {return false; } // Se o tempo de tempo limite for maior que o tempo de rotação, depois de julgar que o fio pode ser suspenso, o segmento será suspenso por um período de tempo se (deve serparafterFailedacquire (p, nó) && nanostimeout> spinfortimeouthreshold) {// tem a corrente para um período de tempo, e depois despertar -se por si só; } // Obtenha o horário atual do sistema Long Now = System.nanotime (); // Tempo de tempo limite é subtraído do intervalo de tempo da aquisição Nanostimeout Nanostimeout - = agora - LastTime; // Atualize a última hora novamente na última hora = agora; // A exceção é lançada quando uma solicitação de interrupção é recebida durante a aquisição do bloqueio if (thread.interrupted ()) {tiro new interruptException (); }}} finalmente {if (falhou) {cancelacquire (nó); }}}A definição da aquisição do tempo limite adquirirá primeiro o bloqueio. Após a primeira vez que a aquisição falhar, ela será baseada na situação. Se o tempo de tempo limite de entrada for maior que o tempo de rotação, o encadeamento será suspenso por um período de tempo, caso contrário, estará girando. Após cada vez que o bloqueio for adquirido, o tempo de tempo limite será subtraído do tempo necessário para adquirir o bloqueio. Até que o tempo limite seja menor que 0, isso significa que o tempo limite foi usado. Em seguida, a operação de adquirir o bloqueio será encerrada e o sinalizador de falhas de aquisição será retornado. Observe que, durante o processo de aquisição do bloqueio com o tempo limite, você pode responder às solicitações de interrupção do thread.
4. Como o fio libera a trava e sai da fila de sincronização?
// Operação de bloqueio de liberação (modo exclusivo) Public Final Boolean Release (int arg) {// gira o bloqueio de senha para ver se ele pode desbloquear se (tryRelease (arg)) {// obtenha o nó da cabeça h = cabeça; // Se o nó da cabeça não estiver vazio e o estado de espera não for igual a 0, acorde o nó sucessor se (h! } retornar true; } return false;} // acorde o nó sucessor privado vazio imparksuccessor (nó nó) {// obtenha o estado de espera do nó dado int ws = node.waitStatus; // Atualize o estado de espera para 0 se (ws <0) {ComparaDSetWaitStatus (nó, ws, 0); } // Obtenha o nó subsequente do nó dado s = node.next; // O nó sucessor está vazio ou o estado de espera é cancelado se (s == null || s.waitStatus> 0) {s = null; // termina o primeiro nó que não é cancelado a partir da fila de travessia para trás para (nó t = cauda; t! }}} // Acorde o primeiro nó após um determinado nó que não é um estado de cancelamento se (s! = Null) {LockSupport.unpark (s.Thread); }}Depois que o tópico mantém a trava na sala, ele fará seu próprio negócio. Depois que o trabalho for concluído, ele liberará a fechadura e sairá da sala. O bloqueio de senha pode ser desbloqueado através do método TryRerelease. Sabemos que o método de tryrelease precisa ser substituído pela subclasse. As regras de implementação de diferentes subclasses são diferentes, o que significa que as senhas definidas por diferentes subclasses são diferentes. Por exemplo, no Reentrantlock, toda vez que a pessoa na sala chama o método de tryrelease, o estado será reduzido em 1, até que o estado seja reduzido para 0, o bloqueio de senha será aberto. Pense se esse processo parece que estamos constantemente girando a roda do bloqueio de senha e o número de rodas é reduzido em 1 cada vez que giramos. O Countdownlatch é um pouco semelhante a este, exceto que não é apenas que está virando uma pessoa, mas que vai mudar uma pessoa, concentrando a força de todos para abrir a fechadura. Depois que o fio sai da sala, encontrará seu assento original, ou seja, encontre o nó da cabeça. Veja se alguém deixou uma pequena nota para ela no assento. Se houver, saberá que alguém está dormindo e precisa pedir para ajudar a acordá -lo e, em seguida, ele acordará esse tópico. Caso contrário, significa que ninguém está esperando na fila de sincronização por enquanto, e ninguém precisa acordar, para que possa sair com tranquilidade. O processo acima é o processo de liberação do bloqueio no modo exclusivo.
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.