Java princípio de programação simultânea 3 (CountDownLatch, Semaphore, CopyOnWriteArrayList, ConcurrentHashMap)

一、CountDownLatch,Semaphore:

1.1 O que é CountDownLatch? Qual o uso? Como a camada inferior é realizada?

A essência do CountDownLatch é na verdade um contador.

Ao processar negócios simultaneamente com vários threads, você precisa aguardar que outros threads concluam o processamento e, em seguida, realizar operações subsequentes, como mesclagem. Ao responder aos usuários, você pode usar CountDownLatch para contagem. Depois que outros threads aparecerem, o thread principal será acordado.

O próprio CountDownLatch é implementado com base no AQS.

Quando novo CountDownLatch, especifique o valor específico diretamente. Este valor será copiado para o atributo de estado.

Quando o thread filho termina de processar a tarefa, ele executa o método countDown e o estado interno é dado diretamente ao estado - 1.

Quando o estado for reduzido a 0, o thread suspenso por await será despertado.

CountDownLatch não pode ser reutilizado e esfriará após o uso.

1.2 O que é um Semáforo? Qual o uso? Como a camada inferior é realizada?

Semaphore é uma classe de ferramenta que pode ser usada para função de limitação de corrente.

Por exemplo, Hystrix envolve isolamento de semáforos, que requer um número limitado de threads concorrentes, para que possa ser implementado usando semáforos.

Por exemplo, se o serviço atual exigir no máximo 10 threads para funcionar ao mesmo tempo, defina o semáforo como 10. Nenhum envio de tarefa precisa obter um semáforo, basta ir trabalhar e retornar o semáforo quando terminar.

O semáforo também é implementado com base no AQS.

Ao construir um semáforo, especifique o número de recursos do semáforo. Ao adquirir, especifique quantos semáforos adquirir. O CAS garante atomicidade e o retorno é semelhante.

1.3 Quando a thread principal terminar, o programa irá parar?

Se o thread principal terminar, mas ainda houver threads de usuário em execução, ele não terminará!

Se o thread principal terminar, o resto são threads daemon, fim!

Dois, CopyOnWriteArrayList:

2.1 Como o CopyOnWriteArrayList garante a segurança do thread? Existem desvantagens?

Quando CopyOnWriteArrayList grava dados, ele garante atomicidade com base em ReentrantLock.

Em segundo lugar, ao gravar dados, uma cópia será copiada e gravada. Após a gravação bem-sucedida, ela será gravada no array em CopyOnWriteArrayList.

Certifique-se de que, ao ler os dados, não haverá inconsistência de dados.

Se a quantidade de dados for relativamente grande, uma cópia precisa ser copiada toda vez que os dados forem gravados, o que ocupa muito espaço. Se a quantidade de dados for relativamente grande, não é recomendável usar CopyOnWriteArrayList.

As operações de gravação são necessárias para garantir a atomicidade, as operações de leitura são garantidas como simultâneas e a quantidade de dados não é grande ~

三、ConcurrentHashMap(JDK1.8)

3.1 Por que o HashMap não é thread-safe?

Questão 1: Existem loops no JDK1.7 (durante a expansão).

Problema 2: Os dados serão substituídos e poderão ser perdidos.

Questão 3: Em segundo lugar, o contador, que também é ++ tradicional, registra imprecisamente ao registrar o número de elementos e o número de vezes que o HashMap é gravado.

Pergunta 4: A migração de dados e a expansão da capacidade também podem resultar em perda de dados.

3.2 Como o ConcurrentHashMap garante a segurança do thread?

1: Tampão traseiro, seguido de expansão com CAS para garantir a segurança da rosca

2: Ao escrever em um array, a segurança é garantida com base no CAS, e quando ele é inserido em uma lista vinculada ou inserido em uma árvore rubro-negra, a segurança é garantida com base no sincronizado.

3: Aqui ConcurrentHashMap é uma tecnologia implementada por LongAdder, e a camada inferior ainda é CAS. (comprimento atômico)

4: Quando o ConcurrentHashMap se expande, um ponto é baseado no CAS para garantir que não haverá problemas de simultaneidade na migração de dados. Em segundo lugar, o ConcurrentHashMap também fornece operações de expansão simultâneas. Por exemplo, o tamanho do array é expandido de 64 para 128. Se dois threads são expandidos ao mesmo tempo,

O encadeamento A recebe a tarefa de migração de dados de 64-48 índices e o encadeamento B recebe a tarefa de migração de dados de índice de 47-32. A chave é que, ao receber tarefas, seja baseado no CAS para garantir a segurança do thread.

3.3 Após a construção do ConcurrentHashMap, o array é criado? Caso contrário, como posso garantir a segurança do thread ao inicializar o array?

ConcurrentHashMap é um mecanismo de carregamento lento e a maioria dos componentes da estrutura são de carregamento lento ~

É baseado no CAS para garantir a segurança do thread de inicialização. Isso não envolve apenas o CAS para modificar a variável sizeCtl para controlar a atomicidade dos dados de inicialização do thread, mas também usa DCL. A camada externa julga que o array não foi inicializado, e o sizeCtl é modificado com base no CAS no meio. Faça um julgamento de array não inicializado.

imagem.png

3.4 Por que o fator de carga é 0,75 e por que a lista encadeada se transforma em uma árvore rubro-negra quando o comprimento atinge 8?

E o fator de carga do ConcurrentHashMap não pode ser modificado!

O fator de carga de 0,75 pode ser explicado de duas maneiras.

Por que não 0,5, por que não 1?

0,5: Se o fator de carga for 0,5, adicionar metade dos dados começará a expandir

  • Vantagens: menos colisões de hash e alta eficiência de consulta.
  • Desvantagens: A expansão é muito frequente e a taxa de utilização do espaço é baixa.

1: Se o fator de carga for 1, os dados são adicionados ao comprimento da matriz para iniciar a expansão

  • Vantagens: Expansão de capacidade pouco frequente, boa utilização de espaço.
  • Desvantagens: Os conflitos de hash serão particularmente frequentes e os dados serão suspensos na lista vinculada, o que afetará a eficiência da consulta, e mesmo a lista vinculada será muito longa para gerar uma árvore rubro-negra, o que afetará a eficiência da gravação .

0,75 pode ser considerado uma escolha intermediária, levando em consideração ambos os aspectos.

Vamos falar sobre a distribuição de Poisson. Quando o fator de carga é 0,75, de acordo com a distribuição de Poisson, a probabilidade de o comprimento da lista vinculada atingir 8 é muito baixa. O logotipo no código-fonte é 0,00000006 e a probabilidade de gerar um árvore rubro-negra é extremamente baixa.

Embora o ConcurrentHashMap introduza árvores rubro-negras, as árvores rubro-negras têm custos de manutenção mais altos para gravação, portanto, você pode usá-las se puder. Os comentários no código-fonte do HashMap também descrevem que as árvores rubro-negras devem ser evitadas o máximo possível.

Quanto à degeneração de 6 em uma lista encadeada, é porque a árvore está cheia de 7 valores, e 7 não é degenerado para evitar conversões frequentes entre listas encadeadas e árvores rubro-negras. Aqui, 6 degenera e deixa um valor intermediário para evite conversões frequentes.

A cena em que a operação de put é muito frequente causará o bloqueio de put durante o período de expansão?

Normalmente não causará bloqueio.

Porque se for descoberto que não há dados na posição atual do índice durante a operação de colocação, os dados serão descartados no array antigo normalmente.

Se durante a operação de colocação, for descoberto que os dados de localização atual foram migrados para a nova matriz e não podem ser inseridos normalmente neste momento, para ajudar a expandir a capacidade, finalize rapidamente a operação de expansão e selecione novamente o índice consulta de posição

3.5 Quando o ConcurrentHashMap será expandido e qual é o processo de expansão?

  • O número de elementos no ConcurrentHashMap atinge o limite para o cálculo do fator de carga e expande diretamente a capacidade
  • Quando o método putAll é chamado para consultar uma grande quantidade de dados, também pode causar uma operação de expansão direta. Se os dados inseridos forem maiores que o limite para a próxima expansão, a grande quantidade de dados será expandida diretamente e depois inserida.
  • Quando o comprimento da matriz for menor que 64 e o comprimento da lista encadeada for maior ou igual a 8, a expansão será acionadaimagem.png

O processo de expansão: (sizeCtl é uma variável do tipo int, usada para controlar a inicialização e expansão)

  • Cada encadeamento de expansão precisa calcular um carimbo de identificação de expansão com base no comprimento de oldTable (para evitar a inconsistência dos comprimentos da matriz de dois encadeamentos de expansão. Em segundo lugar, certifique-se de que os 16 bits do carimbo de identificação de expansão sejam 1, para que um deslocamento à esquerda de 16 bits resultará em um número negativo)
  • A primeira thread expandida terá sizeCtl + 2, o que significa que atualmente existe 1 thread para expandir a capacidade
  • Com exceção da primeira thread de expansão, as demais threads aumentarão sizeCtl + 1, o que significa que outra thread veio para ajudar na expansão
  • O primeiro thread inicializa a nova matriz.
  • Cada thread receberá a tarefa de migrar dados e migrar os dados em oldTable para newTable. Por padrão, cada thread recebe uma tarefa de dados de migração com um comprimento de 16 de cada vez
  • Quando a migração de dados é concluída, quando cada thread vai reivindicar a tarefa novamente, descobre que não há tarefa a receber, então encerra a expansão e define sizeCtl - 1.
  • A última thread que sair da expansão, depois de encontrar -1, sobra 1, e a última thread que sair da expansão irá verificar novamente do começo ao fim, se há algum dado restante que não foi migrado (essa situação basicamente não acontece), após o check , e depois -1, para que o sizeCtl seja deduzido e a expansão seja concluída.

3.6 Como é implementado o contador de ConcurrentHashMap?

Isso é implementado com base no mecanismo do LongAdder, mas não usa diretamente a referência do LongAdder, mas escreve um código com uma similaridade de mais de 80% de acordo com o princípio do LongAdder e o usa diretamente.

O LongAdder usa o CAS para adicionar para garantir a atomicidade e, em segundo lugar, com base em bloqueios de segmento para garantir a simultaneidade.

3.7 A operação de leitura do ConcurrentHashMap será bloqueada?

Não importa onde esteja marcada, ela não será bloqueada.

matriz de consulta? : O primeiro bloco é verificar se o elemento está no array, e então retorná-lo diretamente.

Consultar lista vinculada? : O segundo bloco, se não houver nenhuma situação especial, basta consultar próximo e próximo na lista encadeada.

Ao expandir? : No terceiro bloco, se a posição atual do índice for -1, significa que todos os dados na posição atual foram migrados para a nova matriz e você pode ir diretamente para a nova matriz para consultar, independentemente de a expansão está concluído ou não.

Consulta árvore rubro-negra? : Se um thread está gravando na árvore rubro-negra, o thread de leitura ainda pode consultar a árvore rubro-negra neste momento? Porque a árvore rubro-negra pode ser girada para garantir o equilíbrio, e a rotação mudará o ponteiro, o que pode causar problemas. Portanto, ao converter uma árvore rubro-negra, será mantida não apenas uma árvore rubro-negra, mas também uma lista duplamente encadeada, neste momento a lista duplamente encadeada será consultada para evitar que o thread de leitura seja bloqueado. Quanto a como julgar se existe um thread escrevendo, esperando para escrever ou ler a árvore rubro-negra, julgue de acordo com o lockState do TreeBin, se for 1, significa que existe um thread escrevendo, se for 2, isso significa que há um thread de gravação esperando para escrever, se for 4n , o que significa que existem vários threads fazendo operações de leitura.

Acho que você gosta

Origin blog.csdn.net/lx9876lx/article/details/129116483
Recomendado
Clasificación