No capítulo anterior, introduzimos a fila de bloqueio BlockingQueue. Abaixo, apresentamos sua classe de implementação comumente usada.
1. Use matrizes para implementar filas
Devido aos requisitos especiais da estrutura de dados da fila, é naturalmente adequado ser implementado na forma de uma lista vinculada. Duas variáveis são usadas para gravar o cabeçalho e o final da lista vinculada, respectivamente. Ao excluir ou inserir a fila, basta alterar o cabeçalho ou o final da lista vinculada. Além disso, a lista vinculada está vinculada de maneira referência, portanto sua capacidade é quase ilimitada.
Portanto, como usar as matrizes para implementar filas, precisamos de quatro variáveis: matriz do objeto [] para armazenar elementos na fila, headindex e tailindex registram a cabeça e a cauda da fila e contendo o número de filas.
Uma maneira muito inteligente é usada aqui. Sabemos que quando um elemento é inserido na fila, ele ocupa uma posição na matriz. Quando um elemento é excluído, a posição da matriz é realmente gratuita, indicando que um novo elemento pode ser inserido nessa posição.
Portanto, antes de inserirmos um novo elemento, devemos verificar se a fila está cheia e antes de excluir o elemento, devemos verificar se a fila está vazia.
2. Variáveis importantes de membros no ArrayBlockingQueue
/ ** Armazene os elementos na fila*/ Final Object [] itens; / ** A posição da cabeça da fila*/ int TakeIndex; / ** A posição da cauda da fila*/ int putIndex; / ** O número de elementos na fila atual*/ int contagem; / ** Usado para garantir a segurança de variáveis compartilhadas com vários thread*/ bloqueio de reentrantlock final; / ** Quando a fila estiver vazia, o método de espera do NotEmpty será chamado para tornar o thread atual que aguarde*/ condição final privada não expectativa; / ** Quando a fila estiver cheia, o método de espera de Notfull será chamado, fazendo com que o thread atual aguarde*/ condição final privada Notfull;
Existem mais variáveis de bloqueio, não expectativa e notícia para implementar condições de segurança e thread de vários threads. Como eles operam?
2.1 O papel de bloqueio
Como o ArrayBlockingQueue opera sob vários threads, ao modificar variáveis de membros, como itens, TakeIndex, PutIndex e Count, problemas de segurança com vários threads devem ser considerados. Portanto, o bloqueio exclusivo da trava é usado aqui para garantir a segurança das operações simultâneas.
2.2 O papel do não exato e notoso
Como as filas de bloqueio devem ser implementadas, quando a fila estiver vazia ou a fila está cheia, a operação de leitura ou inserção da fila deve esperar. Por isso, pensamos no objeto de condição sob a estrutura de simultaneidade e o usamos para controlá -lo.
No AQS, apresentamos o papel desta classe:
3. Adicione o método do elemento
3.1 ADD (E E) e Ofereça (E E) Métodos:
// Ligue para o método na classe pai abstrataqueue. public boolean add (e e) {// implementar se (oferta (e)) retornar true; caso contrário, lançar novas ilegalstateException ("fila cheia"); } // Adicione um novo elemento ao final da fila. Retorno true significa que a adição é bem -sucedida, false significa que a adição falhou e nenhuma exceção é lançada em uma oferta pública booleana (e e) {checkNotNull (e); Final ReentrantLock Lock = this.lock; // Use Lock para garantir que a modificação multi-thread dos atributos de membro Lock.lock (); tente {// A fila está cheia e a adição de elementos falha, retorna falsa. if (count == items.length) retornar false; else {// Chame o método de enquá -lo para inserir o elemento na fila Enqueue (e); retornar true; }} finalmente {Lock.unlock (); }}O método ADD é implementado chamando o método de oferta. No método de oferta, é necessário primeiro determinar se a fila está cheia. Se estiver cheio, ele retornará diretamente falso sem bloquear o thread atual. Se você não estiver satisfeito, ligue para o método de enoca para inserir o elemento na fila.
NOTA: O uso do Lock.Lock () aqui garante que apenas um encadeamento Modifys MEMBROM VARIÁVEL ao mesmo tempo para evitar problemas de operação simultâneos. Embora também bloqueie o encadeamento atual, não é uma espera condicional, é apenas porque o bloqueio é mantido por outros threads, e o tempo de operação do método no ArrayBlockingQueue não é longo, o que é equivalente a não bloquear o thread.
3.2 Método de colocar
// Adicione um novo elemento ao final da fila. Se a fila estiver cheia, o thread atual esperará. Resposta à exceção de interrupção Exceção public void put (e e) lança interruptedException {checkNotNull (e); Final ReentrantLock Lock = this.lock; // Use o bloqueio para garantir que os múltiplos threads modifiquem a segurança dos atributos do membro Lock.lockInterruptível (); tente {// Se a fila estiver cheia, ligue para o método notfull.await () para deixar o thread atual aguardar até que a fila não esteja cheia enquanto (conting == items.length) notfull.await (); // Chame o método de enquadrão para inserir o elemento na fila enquistar (e); } finalmente {Lock.unlock (); }}O processo geral do método de oferta é o mesmo que o método de oferta. No entanto, quando a fila estiver cheia, o método notfull.await () será chamado para criar o bloco de encadeamento atual e aguardar até que a fila seja removida por outros threads. Quando a fila estiver insatisfeita, o tópico de espera será despertado.
3.3 Oferta (e e, tempo limite de longa data, unidade de unidade de tempo)
/*** Adicione um novo elemento ao final da fila. Se não houver espaço disponível na fila, o thread atual esperará. * Se o tempo de espera exceder o tempo limite, o False será retornado, indicando que a adição falhou*/ Public Boolean Oferta (e e, tempo limite de longa data, unidade de unidade de tempo) lança interruptedException {checkNotNull (e); // Calcule o valor do tempo do máximo de nanos de espera máximo de nanos = unit.tonanos (tempo limite); Final ReentrantLock Lock = this.lock; // Use o bloqueio para garantir que a modificação multi-thread dos atributos do membro Lock.lockInterruptível (); tente {// a fila está cheia de tempo (contagem == items.length) {// nanos <= 0 significa que o tempo máximo de espera chegou, para que não haja mais necessidade de esperar. Retorne falso, indicando que a adição falhou. if (nanos <= 0) retorna false; // Ligue para o método Notfull.awaitnanos (nanos), o tempo de tempo limite do tempo será despertado automaticamente. // Se for acordado com antecedência, o tempo restante será retornado nanos = notfull.awaitnanos (nanos); } // Chame o método de enoca para inserir o elemento na fila Enqueue (e); retornar true; } finalmente {Lock.unlock (); }}O fluxo geral do método PUT é o mesmo que o método PUT, ele está apenas chamando o método Notfull.awaitnanos (nanos) para fazer com que o thread atual aguarde e definir o tempo de espera.
4. Método de exclusão de elemento
4.1 Métodos Remova () e Poll ():
// Ligue para o método na classe pai abstrataqueue. public e remove () {// implementação chamando a pesquisa e x = poll (); if (x! = nulo) retornar x; caso contrário, lançar novos nosuchElementException (); } // Exclua o primeiro elemento da fila (ou seja, o cabeçalho da fila) e devolva -o. Se a fila estiver vazia, ela não lançará uma exceção, mas retorna nulo. public E Poll () {Final ReentrantLock Lock = this.lock; // Use Lock para garantir que a modificação multi-thread dos atributos de membro Lock.lock (); tente {// se count == 0 e a lista estiver vazia, retorne nulo. Caso contrário, ligue para o método Dequeue para retornar o elemento do cabeçalho da lista (contagem == 0)? nulo: dequeue (); } finalmente {Lock.unlock (); }}O método de remoção é implementado chamando o método Poll (). No método Poll (), se a lista estiver vazia, ele retornará nulo; caso contrário, o método Dequeue será chamado para retornar o elemento do cabeçalho da lista.
4.2 Take () Método
/*** Retorne e remova o primeiro elemento da fila. Se a fila estiver vazia, o fio frontal esperará. Exceção de interrupção da resposta*/ public e the the () lança interruptedException {final reentrantlock bloqueio = this.lock; // Use o bloqueio para garantir que os múltiplos threads modifiquem a segurança dos atributos do membro Lock.lockInterruptível (); tente {// Se a fila estiver vazia, ligue para o método NotEmpty.await () para deixar o thread atual esperar. // Até que outro encadeamento insira elementos na fila, o thread será despertado. while (count == 0) notEmpty.await (); // Ligue para o método dequeue para retornar o elemento do cabeçalho da lista retornar dequeue (); } finalmente {Lock.unlock (); }}Quando o método Take () estiver vazio, o thread atual deve esperar até que outro thread insira um novo elemento na fila e o thread será despertado.
4.3 Enquete (tempo limite de longa data, unidade de unidade de tempo)
/*** Retorne e remova o primeiro elemento da fila. Se a fila estiver vazia, o fio frontal esperará. * Se o tempo de espera exceder o tempo limite, o False será devolvido para indicar que o elemento falha*/ Public E Poll (tempo limite longo, unidade de unidade de tempo) lança interruptedException {// calcule o valor máximo de tempo de espera no total de nanos long nanos = unit.tonsanos (tempo limite); Final ReentrantLock Lock = this.lock; // Use o bloqueio para garantir que a modificação multithread dos atributos de membro Lock.LockInterruptible (); tente {// A fila está vazia enquanto (contagem == 0) {// nanos <= 0 significa que o tempo máximo de espera chegou, para que não haja mais necessidade de esperar, retorne nulo. if (nanos <= 0) retorna nulo; // Ligue para o método NotEmpty.awaitnanos (nanos) para fazer com que o tópico da programação aguarde e defina o tempo limite. nanos = notEmpty.awaitnanos (nanos); } // Ligue para o método dequeue para retornar o elemento do cabeçalho da lista retornar dequeue (); } finalmente {Lock.unlock (); }}Assim como o processo do método Take (), ele é chamado de método Awaitnanos (Nanos) para fazer com que o thread atual aguarde e defina o tempo de espera.
5. Métodos para visualizar elementos
5.1 Métodos de elemento () e Peek ()
// Ligue para o método na classe pai abstrataqueue. public e element () {e x = peek (); if (x! = nulo) retornar x; caso contrário, lançar novos nosuchElementException (); } // Exibir elementos do cabeçalho da fila public e peek () {final reentrantlock bloqueio = this.lock; // Use Lock para garantir que a modificação multi-thread dos atributos de membro Lock.lock (); tente {// retornar o elemento do cabeçalho da fila atual itemat (TakeIndex); // nulo quando a fila está vazia} finalmente {Lock.unlock (); }}Vi. Outros métodos importantes
6.1 Métodos de enquadre e dequeue
// Inserir elemento x na cauda da fila private void enQueue (e x) {// Assert Lock.getholdCount () == 1; // afirma itens [putIndex] == null; // O elemento de posição putIndex atual deve ser o objeto final nulo [] itens = this.items; itens [putIndex] = x; // se putIndex for igual a item.length, redefina o putIndex para 0 se (++ putIndex == items.length) putIndex = 0; // Adicione uma contagem ++ ao número de filas; // Como um elemento é inserido, a fila atual definitivamente não está vazia. Em seguida, acorde um tópico esperando sob a condição noturna não expectativa.Signal (); } // Exclua o elemento do cabeçalho da fila e retorne -o privado e dequeue () {// Assert Lock.getholdCount () == 1; // afirma itens [TakeIndex]! = NULL; objeto final [] itens = this.items; // Obtenha o elemento do cabeçalho da fila atual @suppresswarnings ("desmarcado") e x = (e) itens [TakeIndex]; // Defina a posição atual do cabeçalho da fila como itens nulos [TakeIndex] = NULL; if (++ TakeIndex == items.length) TakeIndex = 0; // menos o número de filas por uma contagem--; if (itrs! = null) itrs.ElementDequeed (); // Como um elemento é excluído, a fila definitivamente não é satisfeita; portanto, acorde um fio esperando sob a condição noturna notfull.Signal (); retornar x; }Esses dois métodos representam, inserindo elementos e removendo elementos da fila, respectivamente. E eles querem acordar o tópico de espera. Depois de inserir um elemento, a fila não deve estar vazia; portanto, o fio aguardando sob a condição notável deve ser despertada. Depois de excluir o elemento, a fila deve ser insatisfeita; portanto, o fio aguardando sob a condição noturna deve ser despertado.
6.2 Remover (objeto O) Método
// Exclua o elemento do objeto o na fila e exclua no máximo um booleano público Remova (objeto o) {if (o == null) retorna false; objeto final [] itens = this.items; // Use o bloqueio para garantir a segurança da modificação multithread dos atributos do membro Reentrantlock Lock = this.lock; Lock.lock (); tente {// excluir apenas quando houver um valor na fila. if (contagem> 0) {// a próxima posição no final da fila final int putIndex = this.putIndex; // a posição do cabeçalho da fila int i = TakeIndex; do {// O elemento de posição atual é o mesmo que o elemento excluído se (O.Equals (itens [i])) {// excluir o elemento I Removeat Removeat (i); // retorna true retorno true; } if (++ i == items.length) i = 0; // Quando i == putIndex significa que todos os elementos foram atravessados} while (i! = PutIndex); } retornar false; } finalmente {Lock.unlock (); }} Exclua o objeto especificado O da fila, então você deve atravessar a fila e excluir o primeiro elemento que é o mesmo que o objeto o. Se não houver um elemento objeto o na fila, retorne false para excluir falhas.
Aqui estão dois pontos a serem observados:
Como atravessar uma fila é atravessar a cabeça da fila até o final da fila. Depende das duas variáveis que TakeIndex e PutIndex.
Por que o objeto [] itens = this.items; Este código não é colocado no bloco de código de bloqueio síncrono. Os itens são variáveis de membros. Quando houver tantos tópicos, não haverá problemas de simultaneidade?
Isso ocorre porque os itens são variáveis de referência, não tipos básicos de dados, e nossas operações de inserção e exclusão nas filas são todas para esta matriz de itens, e a referência à matriz não é alterada. Portanto, no código de bloqueio, os itens receberão as mais recentes modificações por outros threads. Mas se o int putIndex = this.putIndex; O método bloqueia o bloco de código fora, ocorrerá um problema.
Removeat (método Final Int RemofIndex)
// Exclua o elemento na fila Remova a posição do ILOVIDO REMOVEAT (FINAL INT REMOTIMINDEX) {// Assert Lock.getholdCount () == 1; // afirma itens [removerndex]! = null; // assert removerndex> = 0 && removerndex <items.length; objeto final [] itens = this.items; // significa que é muito mais fácil excluir o elemento como cabeçalho da lista, que é semelhante ao processo do método dequeue se (removendex == TakeIndex) {// remove o elemento nos itens de posição removendex [TakeIndex] = null; // Quando o final da matriz, você precisa ir para a posição do cabeçalho da matriz se (++ TakeIndex == items.length) TakeIndex = 0; // menos o número de filas por uma contagem--; if (itrs! = null) itrs.ElementDequeed (); } else {// um "interior" remove final int putIndex = this.putIndex; para (int i = removendex ;;) {int a próxima = i + 1; if (next == items.length) próximo = 0; // O final da fila ainda não atingiu o final da fila, então o elemento na próxima posição é substituído pelo elemento na posição anterior se (a seguir! = PutIndex) {itens [i] = itens [a seguir]; i = próximo; } else {// Defina o elemento da cauda dos itens nulos da fila [i] = null; // Redefina o valor de putIndex this.putIndex = i; quebrar; }} // diminui o número de filas por contagem-; if (itrs! = null) itrs.RemovedAt (removendex); } // Como um elemento é excluído, a fila definitivamente não é satisfeita; portanto, acorde um fio esperando sob a condição noturna notfull.Signal (); }Exclua elementos no local especificado na fila. Deve -se notar que a matriz após a exclusão ainda pode manter a forma da fila, que é dividida em duas situações:
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.