No código do programa C, podemos usar o bloqueio mutex fornecido pelo sistema operacional para obter acesso mutex a blocos síncronos e bloqueio de threads e trabalho de despertar. No entanto, em Java, além de fornecer Lockapi, as palavras -chave sincronizadas também são fornecidas no nível de sintaxe para implementar as primitivas de sincronização mutex. Então, como você implementa a chave sincronizada na JVM?
1. Representação de bytecode sincronizada:
Existem duas sintaxes sincronizadas internas na linguagem Java: 1. Declarações sincronizadas; 2. Método sincronizado. Para instruções sincronizadas quando o código -fonte Java é compilado no bytecode por instruções JAVAC, MonitorEnter e Monitorexit Bytecode serão inseridas, respectivamente, nas posições de entrada e saída do bloco de sincronização. O método sincronizado será traduzido em instruções de chamadas e retorno de métodos comuns, como: InvokeVirtual e Areturn Instruções. Não há instrução especial no nível da VM ByteCode para implementar o método modificado sincronizado. Em vez disso, a posição 1 do sinalizador sincronizada 1 no campo Method Access_Flags do método é colocada na tabela de métodos do arquivo de classe, indicando que o método é um método sincronizado e usa o objeto que chama o método ou a classe pertencente ao método para representar Klass como um objeto de trava.
2. Otimização de bloqueios na JVM:
Simplificando, o MonitorEnter e o Monitorexit Bytecode na JVM depende do MUTEXLOCK do sistema operacional subjacente para implementá -lo. No entanto, como o uso do mutexlock requer suspender o encadeamento atual e a troca do estado do usuário para o estado do kernel para executar, essa comutação é muito cara; No entanto, na maioria dos casos, na realidade, o método de sincronização é executado em um ambiente de thread único (ambiente de competição sem trava). Se o mutexlock for chamado a cada vez, afetará seriamente o desempenho do programa. No entanto, no JDK1.6, muitas otimizações foram introduzidas na implementação de bloqueios, como bloqueio de trava, eliminação de trava, travamento leve, travamento tendencioso, rotação adaptativa e outras tecnologias para reduzir a sobrecarga da operação de bloqueio.
Co -cozimento: isto é, reduza as operações desnecessárias de trava e bloqueio, expandam vários bloqueios contínuos em uma trava com uma faixa maior.
Eliminação da trava: Através da análise de fuga pelo compilador JIT de tempo de execução, alguma proteção de bloqueio é eliminada. Alguns dados que não são compartilhados por outros threads fora do bloco de sincronização atual. Através da análise de escape, o espaço do objeto pode ser alocado na pilha local do thread (ao mesmo tempo, também pode reduzir a sobrecarga de coleta de lixo na pilha).
Locking Locking: A implementação desse bloqueio é baseada no pressuposto de que, em casos reais, a maioria do código de sincronização em nosso programa geralmente está em um estado de competição sem trava (ou seja, um ambiente de execução de thread único). No caso de concorrência livre de bloqueio, isso pode evitar chamar a chamada de mutexes pesados no nível do sistema operacional. Em vez disso, no MonitorEnter e Monitorexit, você só precisa confiar em uma instrução atômica do CAS para concluir a aquisição e liberação do bloqueio. Quando houver concorrência de bloqueio, o thread que não executa as instruções do CAS chama o sistema operacional MUTEX para entrar no estado de bloqueio e acordar quando o bloqueio for liberado (as etapas específicas do processamento são discutidas em detalhes abaixo).
Lock de polarização: é para evitar a execução de instruções atômicas desnecessárias do CAS durante a aquisição de bloqueio no caso de concorrência livre de trava, porque, embora as instruções atômicas do CAS sejam relativamente pequenas em custo em comparação com os bloqueios pesados, eles ainda têm atrasos locais muito consideráveis (consulte este artigo).
Giratório adaptável: quando um encadeamento não executa a operação do CAS durante a aquisição de uma trava leve, ele entra em uma espera ocupada antes de entrar na trava pesada do sistema operacional (mutexsemáphore) associada ao monitor e, em seguida, tente novamente. Se ainda falhar após um certo número de tentativas, o semáforo associado ao monitor (ou seja, bloqueio mutex) é chamado para entrar no estado de bloqueio.
3. Objectheader:
Ao criar um objeto na JVM, dois cabeçalhos de objeto do tamanho de palavras serão adicionados na frente do objeto. Uma palavra na máquina de 32 bits é de 32 bits. Diferentes conteúdos são armazenados no Markworld de acordo com diferentes bits de status. Conforme mostrado na figura acima, em uma trava leve, o Markword é dividido em duas partes. No início, o Lockword está definido como HashCode e os três bits mais baixos representam o estado em que o Lockword está localizado. O estado inicial é 001, o que significa o estado livre de bloqueio. Klassptr aponta para o endereço representado pelo objeto cuja classe bytecode está dentro da máquina virtual. Os campos representam campos de instância de objeto contínuo.
4. MonitorreCord:
MonitorreCord é uma estrutura de dados privada dos threads. Cada encadeamento possui uma lista de MonitorreCords disponíveis e uma lista global disponível. Então, quais são os usos desses monitorreRecords? Cada objeto bloqueado estará associado a um MonitorreCord (Lockword no cabeçalho do objeto aponta para o endereço de início do MonitorreCord. Como esse endereço está alinhado em 8byte, os três mais baixos de bloqueio de bloqueio podem ser usados como bits de status). Ao mesmo tempo, existe um campo proprietário em MonitorreCord para armazenar o identificador exclusivo do thread que possui a trava, indicando que a trava está ocupada por este thread. A figura a seguir mostra a estrutura interna do MonitorreCord:
Proprietário: NULL No início significa que nenhum thread atualmente possui o registro do monitor. Quando o thread possui com sucesso o bloqueio, ele salva a identidade exclusiva do thread e, quando o bloqueio é liberado, está definido como nulo;
Entrada: Associe um sistema mutex (semáforo) para bloquear todos os threads que não conseguem bloquear o registro do monitor.
RcThis: representa o número de todos os threads bloqueados ou aguardando o registro do monitor.
NEST: Usado para implementar a contagem de bloqueios de reentrada.
HashCode: salva o valor do hashcode copiado do cabeçalho do objeto (também pode conter a idade do GC).
Candidato: Usado para evitar bloqueio desnecessário ou aguardar os threads acordarem, porque apenas um thread pode possuir com êxito a fechadura a cada vez. Se o thread anterior que libera a trava acordar todos os threads de bloqueio ou espera a cada vez, causará troca de contexto desnecessária (do bloqueio para o pronto e o bloqueio novamente devido à falha da bloqueio da competição) e, portanto, levará a uma degradação severa de desempenho. O candidato possui apenas dois valores possíveis: 0 significa que não há thread que precise ser acordado até 1 significa acordar um thread sucessor para competir pelo bloqueio.
5. Implementação específica de bloqueios leves:
Um encadeamento pode bloquear um objeto de duas maneiras: 1. Obtenha a trava do objeto expandindo um objeto em um estado livre de bloqueio (bit de status 001); 2. O objeto já está em um estado expandido (bit de status 00), mas o campo proprietário do registro do monitor apontado pelo Lockword é nulo, para que você possa tentar definir diretamente o proprietário como sua própria identidade através da instrução atômica do CAS para obter o bloqueio.
O processo geral de obtenção de um bloqueio (monitorente) é o seguinte:
(1) Quando o objeto está em um estado sem bloqueio (o valor da palavra de registro é hashcode, o bit de status é 001), o thread obtém um registro de monitor gratuito em sua lista de registros de monitor disponível. Os valores iniciais do ninho e do proprietário são predefinidos a 1 e a própria identificação do thread, respectivamente. Once the monitor record is ready, we install the monitor record's start address to the LockWord field of the object header through the CAS atomic instruction to expand (the original text is inflate. I think the reason why it is called inflate is mainly because the object is expanded after it is expanded; for spatial efficiency, the monitor is used to expand the size of the object; The record structure is extracted from the object header and only attached to the object when needed. However, it is a bit contradictory Para este documento.
(2) O objeto foi expandido e o thread salvo no proprietário é identificado como o thread que adquire o próprio bloqueio. Este é o caso de bloqueios reentrantes. Você só precisa simplesmente adicionar 1 ao ninho. Nenhuma operação atômica é necessária e muito eficiente.
(3) O objeto foi expandido, mas o valor do proprietário é nulo. Esse estado ocorre quando um thread bloqueando ou aguardando um bloqueio é bloqueado ao mesmo tempo, o proprietário anterior do bloqueio acaba de liberar o bloqueio. No momento, vários threads tentam definir o proprietário como sua própria identidade através da instrução atômica do CAS para obter o bloqueio em um estado de competição com vários threads. O thread que não competirá entrará no caminho de execução do quarto caso (4).
(4) O objeto está em um estado expandido e o proprietário não é nulo (bloqueado). Ele gira um certo número de vezes antes de chamar o mutex dos pesos pesados do sistema operacional. Quando um certo número de vezes é atingido, se o bloqueio ainda não for obtido com sucesso, é hora de começar a entrar no estado de bloqueio. Primeiro, adicione o valor de RFThis atomicamente em 1. Como outros threads podem destruir a relação entre o objeto e o registro do monitor durante a adição de 1, é necessário executar outra comparação após a adição de 1 para garantir que o valor do Lockword não tenha sido alterado. Quando se descobriu que foi alterado, o processo do monitorente deve ser repetido. Ao mesmo tempo, é observado novamente se o proprietário é nulo. Nesse caso, ligará para o CAS para participar do bloqueio da competição. Se a competição de bloqueio falhar, ele entrará em um estado de bloqueio.
O processo geral de liberação da trava (monitorexit) é o seguinte:
(1) Primeiro, verifique se o objeto está em um estado expandido e o thread é o proprietário do bloqueio. Se for constatado que está errado, uma exceção será lançada;
(2) Verifique se o campo de ninho é maior que 1. Se for maior que 1, basta reduzir o ninho em 1 e continuar a ter a trava. Se for igual a 1, digite a etapa (3);
(3) Verifique se o RFThis é maior que 0, defina o proprietário como NULL e acorde um thread de bloqueio ou espera para tentar adquirir a fechadura novamente. Se for igual a 0, ele entrará na etapa (4)
(4) Deflatar um objeto, solte o bloqueio substituindo a palavra de bloqueio do objeto de volta ao valor original do HashCode para liberar o bloqueio e colocar o registro do monitor de volta ao thread.
Resumir
Referência: " Compreensão aprofundada dos recursos avançados e melhores práticas da Java Virtual Machine JVM (Zhou Zhiming) "
O exposto acima é o conteúdo inteiro deste artigo sobre análise de problemas sincronizados e de implementação dos detalhes da JVM. Espero que seja útil para todos. Se houver alguma falha, deixe uma mensagem para apontá -la. Obrigado amigos pelo seu apoio para este site!