1. Lock de peso pesado
No artigo anterior, introduzimos o uso de sincronizados e seus princípios de implementação. Agora, devemos saber que o sincronizado é implementado através de uma trava de monitor dentro do objeto. No entanto, o bloqueio do monitor é essencialmente implementado, contando com o bloqueio mutex do sistema operacional subjacente. O sistema operacional precisa alternar entre os threads do estado do usuário para o estado principal. Isso é muito caro, e a conversão entre os estados leva um tempo relativamente longo. É por isso que o sincronizado é ineficiente. Portanto, chamamos esse bloqueio que depende da implementação do sistema operacional MUTEX Lock "A Heavyweight Lock". O núcleo das várias otimizações feitas para sincronizadas no JDK é reduzir o uso dessa trava pesada. Após o JDK1.6, a fim de reduzir o consumo de desempenho causado pela obtenção e liberação de bloqueios e melhorar o desempenho, foram introduzidos o desempenho "bloqueios leves" e "bloqueios tendenciosos".
2. Bloqueio leve
Existem quatro tipos de estados de trava: estado sem trava, trava tendenciosa, trava leve e trava pesada. Com a concorrência de bloqueios, os bloqueios podem ser atualizados de bloqueios tendenciosos para bloqueios leves e depois atualizados travas pesadas (mas a atualização de bloqueios é unidirecional, o que significa que eles só podem atualizar de baixo para alto e não haverá degradação de bloqueios). No JDK 1.6, o bloqueio de polarização e o bloqueio leve são ativados por padrão. Também podemos desativar o preconceito bloqueado por -xx: -UseBIASEDLOCK. O estado de bloqueio é salvo no arquivo de cabeçalho do objeto, tomando o JDK de 32 bits como exemplo:
Status de bloqueio | 25 bits | 4 bits | 1 bit | 2 bits | ||
23 bits | 2 bits | É travada tendenciosa? | Bit da bandeira de trava | |||
Trava leve | Ponteiro para bloquear registros na pilha | 00 | ||||
Trava dos pesos pesados | Ponteiro para Mutex (trava dos pesos pesados) | 10 | ||||
Tags gc | nulo | 11 | ||||
Bloqueio positivo | ID do tópico | Época | Os assuntos têm idade | 1 | 01 | |
Sem trava | HashCode de objeto | Os assuntos têm idade | 0 | 01 | ||
"Lightweight" é relativo às bloqueios tradicionais que usam o sistema operacional mutexes. No entanto, é importante enfatizar primeiro que os bloqueios leves não são usados para substituir os bloqueios pesados. Sua intenção original é reduzir o consumo de desempenho gerado pelo uso de bloqueios tradicionais de pesos pesados sem concorrência com vários threads. Antes de explicar o processo de execução de bloqueios leves, primeiro entendemos que os cenários adaptados aos bloqueios leves são o caso em que os threads executam alternadamente os blocos síncronos. Se o mesmo bloqueio for acessado ao mesmo tempo, o bloqueio leve se expandirá para uma trava pesada.
1. O processo de travamento da trava leve
(1) Quando o código entra no bloco de sincronização, se o estado de bloqueio do objeto de sincronização estiver livre de bloqueio (o sinalizador de bloqueio for "01", se é o bloqueio tendencioso é "0"), a máquina virtual primeiro criará um espaço de bloqueio chamado Record no quadro de pilha do fio atual para armazenar a cópia atual da palavra atual do objeto, que é oficialmente chamado de Word. No momento, o estado da pilha de encadeamento e o cabeçalho do objeto é mostrado na Figura 2.1.
(2) Copie a palavra Mark no cabeçalho do objeto e copie -o para o registro de bloqueio.
(3) Depois que a cópia for bem -sucedida, a máquina virtual usará as operações do CAS para tentar atualizar a palavra Mark do objeto para um ponteiro para bloquear o registro e apontar o ponteiro do proprietário no registro de bloqueio para objeto Mark Word. Se a atualização for bem -sucedida, execute a etapa (3), caso contrário, execute a etapa (4).
(4) Se essa ação de atualização for bem -sucedida, o encadeamento terá o bloqueio do objeto e o sinalizador de trava da palavra Marca do objeto é definido como "00", o que significa que o objeto está em um estado de bloqueio leve. Neste momento, o estado da pilha de thread e a cabeça do objeto é mostrada na Figura 2.2.
(5) Se esta operação de atualização falhar, a máquina virtual verificará primeiro se a palavra Mark do objeto aponta para o quadro da pilha do thread atual. Nesse caso, isso significa que o encadeamento atual já possui o bloqueio do objeto e pode entrar diretamente no bloco de sincronização para continuar a execução. Caso contrário, vários threads competem por bloqueios, e a trava leve se expandirá para uma trava pesada e o valor do estado da bandeira de trava se tornará "10". O ponteiro para a trava dos pesos pesados (Mutex) é armazenado no Mark Word, e o fio que aguarda a trava também entrará no estado de bloqueio. O thread atual tenta usar o spin para adquirir o bloqueio. A rotação é evitar bloquear a linha e usar um loop para adquirir a trava.
Figura 2.1 O estado de pilha e objeto antes da operação leve da CAS de bloqueio
Figura 2.2 O estado de pilha e objeto após a operação leve de Lock CAS
2. Processo de desbloqueio de bloqueio leve:
(1) Tente substituir o objeto Mark Word deslocado copiado no encadeamento através da operação CAS.
(2) Se a substituição for bem -sucedida, todo o processo de sincronização será concluído.
(3) Se a substituição falhar, significa que outros threads tentaram adquirir o bloqueio (o bloqueio foi expandido no momento), o encadeamento suspenso deve ser despertado durante a liberação da fechadura.
3. Lock positivo
The introduction of bias lock is to minimize unnecessary lightweight lock execution paths without multi-thread competition, because the acquisition and release of lightweight locks depend on multiple CAS atomic instructions, while bias locks only need to rely on one CAS atomic instructions when replacing ThreadID (because the bias lock must be cancelled once a multi-thread competition occurs, so the performance loss of the cancel operation of bias lock must be less than the saved performance consumption de instruções atômicas do CAS). Como mencionado acima, os bloqueios leves são usados para melhorar o desempenho quando os threads executam alternadamente os blocos síncronos, enquanto bloqueios tendenciosos são usados para melhorar ainda mais o desempenho quando apenas um encadeamento executa blocos síncronos.
1. O processo tendencioso de aquisição de bloqueio:
(1) Acesse se o sinalizador do bloqueio de viés no Mark Word está definido como 1 e se o sinalizador de bloqueio é 01 - confirme que é um estado tendencioso.
(2) Se for um estado tendencioso, teste se o ID do encadeamento aponta para o encadeamento atual. Se for, digite a etapa (5); caso contrário, digite a etapa (3).
(3) Se o ID do thread não apontar para o encadeamento atual, o bloqueio será competido pela operação do CAS. Se a concorrência for bem -sucedida, defina o ID do thread em Mark Word no ID do thread atual e execute (5); Se a competição falhar, execute (4).
(4) Se o CAS falhar em adquirir trava de viés, significa que há concorrência. Quando o SafePoint Global é atingido, o encadeamento que obtém a trava do viés é suspenso. O bloqueio de viés é atualizado para uma trava leve e o fio bloqueado no SafePoint continua a executar o código de sincronização.
(5) Execute o código de sincronização.
2. Liberação da trava tendenciosa:
A revogação da trava tendenciosa é mencionada no quarto passo acima. A trava tendenciosa soltará apenas a trava quando outros threads tentarem competir pela trava tendenciosa, e a rosca não liberará ativamente a trava tendenciosa. O cancelamento do bloqueio tendencioso requer a espera do ponto de segurança global (nenhum bytecode está sendo executado neste momento). Primeiro, ele vai pausar a rosca com a trava tendenciosa, determinar se o objeto de trava está em um estado bloqueado e depois retornará ao desbloqueado (o bit de bandeira é "01") ou a trava leve (o bit de sinalizador é "00") após a desfazer a trava tendenciosa.
3. Conversão entre a trava dos pesos pesados, a trava leve e a trava do viés
Figura 2.3 O diagrama de conversão dos três
Esta imagem é principalmente um resumo do conteúdo acima. Se você tem um bom entendimento do conteúdo acima, a imagem deve ser fácil de entender.
4. Outras otimizações
1. Fiação adaptativa: a partir do processo de obtenção de bloqueios leves, sabemos que quando um thread não executa a operação CAS durante a aquisição de bloqueios leves, é necessário obter a trava pesada através da rotação. O problema é que o spin requer consumo de CPU. Se o bloqueio não puder ser obtido, o encadeamento estará em um estado de rotação e resíduos de recursos da CPU em vão. A maneira mais fácil de resolver esse problema é especificar o número de giros, por exemplo, permitir que o ciclo 10 vezes e insira um estado de bloqueio se a trava não for obtida. Mas o JDK adota uma abordagem mais inteligente - giro adaptável. Simplificando, se o encadeamento for bem -sucedido, o número de giros será mais na próxima vez e, se o spin falhar, o número de giros será reduzido.
2. Bloqueio de bloqueio: O conceito de bloqueio de bloqueio deve ser mais fácil de entender, que é mesclar as operações de trava e desbloquear conectadas várias vezes em uma vez, expandindo vários bloqueios contínuos em uma trava com uma faixa maior. Por exemplo:
pacote com.paddx.test.string; public class stringbufferest {stringbuffer stringbuffer = new stringBuffer (); public void Append () {StringBuffer.Append ("A"); stringbuffer.append ("b"); stringbuffer.append ("c"); }}Aqui, toda vez que você chama o método StringBuffer.Append, o bloqueio e o desbloqueio são necessários. Se a máquina virtual detectar uma série de operações de bloqueio e desbloqueio no mesmo objeto, ela o mesclará em uma gama maior de operações de bloqueio e desbloqueio, ou seja, o bloqueio será executado no primeiro método de apêndice e o desbloqueio será realizado após a conclusão do último método do Apênd.
3. Eliminação de bloqueio: eliminação de bloqueio significa remover operações desnecessárias de bloqueio. De acordo com a técnica de escape de código, se estiver determinado que um pedaço de código e os dados na pilha não escaparão do encadeamento atual, pode-se considerar que esse pedaço de código é seguro e não há necessidade de travá-lo. Veja o seguinte programa:
pacote com.paddx.test.concurrent; public class SynchronizedTest02 {public static void main (string [] args) {synchronizedTest02 test02 = new SynchronizedTest02 (); // Inicie o aquecimento para (int i = 0; i <10000; i ++) {i ++; } long start = system.currenttimemillis (); for (int i = 0; i <100000000; i ++) {test02.append ("abc", "def"); } System.out.println ("time =" + (System.currenttimemillis () - start)); } public void Append (string str1, string str2) {stringbuffer sb = new StringBuffer (); sb.append (str1) .append (str2); }}Embora o anexo do StringBuffer seja um método síncrono, o StringBuffer neste programa pertence a uma variável local e não escapará do método. Portanto, esse processo é na verdade seguro e pode eliminar a trava. Aqui está o resultado da minha execução local:
Para minimizar o impacto de outros fatores, o bloqueio de polarização (-xx: -UseBIASEDLocking) está desativado aqui. Através do programa acima, pode -se observar que o desempenho foi bastante aprimorado após a eliminação da fechadura.
Nota: Os resultados da execução podem ser diferentes entre diferentes versões do JDK. A versão JDK que eu uso aqui é 1.6.
5. Resumo
Este artigo se concentra na otimização do sincronizado no JDK, como bloqueios leves e bloqueios tendenciosos, mas esses dois bloqueios não estão completamente sem deficiências. Por exemplo, quando a concorrência é feroz, ela não apenas falha em melhorar a eficiência, mas reduzirá a eficiência, porque há um processo adicional de atualização de bloqueio. Neste momento, é necessário desativar bloqueios tendenciosos através de -xx: -UsebiasEdLocking. Aqui está uma comparação dessas bloqueios:
Trancar | vantagem | falha | Cenários aplicáveis |
Bloqueio positivo | O bloqueio e o desbloqueio não requer consumo adicional, e há uma lacuna de nanossegundos em comparação com a execução de um método assíncrono. | Se houver concorrência de bloqueio entre os threads, causará um consumo adicional de revogação de bloqueio. | Adequado para cenários em que apenas um thread acessa o bloco síncrono. |
Trava leve | Os threads concorrentes não bloquearão, o que melhora a velocidade de resposta do programa. | Se o thread que nunca conseguir a competição de bloqueio consumirá a CPU. | Perseguindo o tempo de resposta. A execução do bloco síncrono é muito rápida. |
Trava dos pesos pesados | A competição de threads não usa spin e não consome CPU. | Bloqueio de rosca, tempo de resposta lenta. | Perseguindo a taxa de transferência. A velocidade de execução do bloco de sincronização é relativamente longa. |