Para usar uma analogia: um objeto é como uma casa grande, a porta está sempre aberta. Existem muitos cômodos (também conhecidos como métodos) em uma casa. Essas salas estão bloqueadas (método sincronizado) ou desbloqueadas (método normal). Há uma chave na porta da sala. Essa chave pode abrir todas as salas trancadas. Além disso, comparo todos os threads que desejam chamar métodos deste objeto com pessoas que desejam entrar em determinado cômodo desta casa. Isso é tudo, vamos dar uma olhada em como essas coisas funcionam umas com as outras.
Aqui primeiro esclarecemos nossos pré-requisitos. O objeto possui pelo menos um método sincronizado, caso contrário esta chave não tem significado. Claro, não haverá esse nosso tema.
Uma pessoa quer entrar em um quarto trancado. Ela chega até a porta da casa e vê a chave ali (indicando que ninguém mais quer usar o quarto trancado ainda). Então ele subiu, pegou as chaves e usou os quartos conforme planejado. Observe que ele devolve a chave imediatamente após cada uso da sala trancada. Mesmo que ele queira usar dois quartos trancados seguidos, ele terá que devolver a chave e recuperá-la no meio.
Portanto, o princípio do uso de chaves em circunstâncias normais é: "Pegue-as emprestadas à medida que as usa e devolva-as assim que as usar."
Neste momento, outras pessoas podem usar os quartos desbloqueados sem restrições. Uma pessoa pode usar um quarto ou duas pessoas podem usar um quarto. Mas se alguém quiser entrar numa sala trancada, tem que correr até a porta e olhar. Se você tiver a chave, é claro que pode pegá-la e ir embora. Se não tiver a chave, basta esperar.
Se muitas pessoas estiverem esperando pela chave, quem a receberá primeiro quando a chave for devolvida? Não garantido. Assim como o cara do exemplo anterior que quer usar dois quartos trancados seguidos, se houver outras pessoas esperando pela chave quando ele a devolver, não há garantia de que esse cara conseguirá pegá-la novamente. (A especificação JAVA afirma claramente em muitos lugares que não há garantias, como quanto tempo levará para Thread.sleep() retornar à execução após um descanso, qual thread com a mesma prioridade será executado primeiro e quando o bloqueio para acessar o objeto é liberado, vários threads no pool de espera serão Qual thread irá obtê-lo primeiro? Etc. Acho que a decisão final cabe à JVM. A razão pela qual não há garantia é porque quando a JVM toma a decisão acima, ela não faz um julgamento simplesmente com base em uma condição, mas com base em muitas condições. muitas condições de julgamento. Se você as disser, isso pode afetar o JAVA. A promoção pode ser devida à proteção da propriedade intelectual, a SUN não deu nenhuma garantia e é compreensível, mas acredito que essas incertezas não são completamente incertas porque o próprio computador funciona de acordo com as instruções. Existem regras a serem encontradas. Qualquer pessoa que tenha estudado computadores sabe que o nome científico dos números aleatórios nos computadores são números pseudo-aleatórios, que são escritos por pessoas que usam um determinado método. certo. É muito problemático e não faz muito sentido, então se você não tiver certeza, não tenha certeza.)
Vejamos novamente os blocos de código sincronizados. É um pouco diferente do método de sincronização.
1. Em termos de tamanho, os blocos de código sincronizados são menores que os métodos sincronizados. Você pode pensar em um bloco de código sincronizado como um espaço em uma sala desbloqueada, separado por uma tela bloqueada.
2. O bloco de código de sincronização também pode especificar manualmente a chave de outro objeto. É como especificar qual chave pode ser usada para abrir a fechadura desta tela. Você pode usar a chave desta casa, você também pode especificar a chave de outra casa para abri-la. para abrir a fechadura, pegue essa chave e use a chave da casa para abrir a tela bloqueada desta casa.
Lembre-se de que a chave de outra casa que você obteve não impede que outras pessoas entrem nos cômodos desbloqueados daquela casa.
Por que usar blocos de código sincronizados? Acho que deveria ser assim: em primeiro lugar, a parte de sincronização do programa afeta a eficiência operacional, e um método geralmente cria algumas variáveis locais primeiro e depois executa algumas operações nessas variáveis, como cálculos, exibição, etc. ; e coberturas de sincronização Quanto mais código houver, mais sério será o impacto na eficiência. Por isso, geralmente tentamos manter o alcance do seu impacto tão pequeno quanto possível. Como fazer isso? Blocos de código sincronizados. Sincronizamos apenas as partes de um método que deveriam ser sincronizadas, como operações.
Além disso, o bloco de código de sincronização pode especificar a chave. Esse recurso tem o benefício adicional de ocupar a chave de um objeto dentro de um determinado período de tempo. Lembre-se do que eu disse antes sobre os princípios do uso de chaves em circunstâncias normais. Estas não são circunstâncias comuns. A chave obtida não é retornada para sempre, mas somente quando você sai do bloco de código sincronizado.
Vamos usar a analogia do cara que queria usar duas salas trancadas seguidas. Como posso continuar a usar outro quarto depois de usar um quarto? Use blocos de código sincronizados. Primeiro crie outro thread, faça um bloco de código de sincronização e aponte a fechadura desse bloco de código para a chave da casa. Então comece esse tópico. Contanto que você consiga pegar a chave da casa ao entrar nesse bloco de código, você pode mantê-la até sair desse bloco de código. Em outras palavras, você pode até percorrer todos os quartos trancados deste quarto, e até dormir (10*60*1000), mas ainda há 1000 threads esperando pela chave na porta. É bastante agradável.
Vamos falar sobre a correlação entre o método sleep() e a chave. Se um thread for forçado a dormir() após obter a chave e não tiver concluído o conteúdo da sincronização, a chave ainda estará lá. A chave não será devolvida até que seja executada novamente e todo o conteúdo da sincronização seja concluído. Lembre-se, o cara simplesmente se cansou de trabalhar e foi descansar. Ele não terminou o que queria fazer. Para evitar que outras pessoas entrassem no quarto e fizessem bagunça por dentro, ele tinha que usar a única chave do corpo mesmo durante o sono.
Finalmente, alguém pode perguntar: por que precisamos de uma chave para abrir cada porta, em vez de uma chave para cada porta? Acho que isso é puramente uma questão de complexidade. Uma chave para uma porta é certamente mais segura, mas envolverá muitos problemas. A geração, armazenamento, aquisição, devolução de chaves, etc. Sua complexidade pode aumentar geometricamente à medida que aumenta o número de métodos de sincronização, afetando seriamente a eficiência. Isto pode ser considerado como uma questão de trade-off. Quão indesejável é reduzir bastante a eficiência para aumentar um pouco a segurança.