Vamos primeiro ler uma explicação detalhada de sincronizado:
Sincronizado é uma palavra -chave no idioma Java. Quando é usado para modificar um método ou um bloco de código, ele pode garantir que, no máximo, um thread execute o código ao mesmo tempo.
1. Quando dois threads simultâneos acessam este bloco de código sincronizado (this) sincronizado no mesmo objeto, apenas um thread pode ser executado dentro de um tempo. Outro thread deve esperar que o encadeamento atual execute este bloco de código antes de poder executar o bloco de código.
2. No entanto, quando um thread acessa um bloco de código de sincronização sincronizado (this) de um objeto, outro thread ainda pode acessar o bloco de código de sincronização não sincronizado (this) nesse objeto.
3. É particularmente crítico que, quando um encadeamento acessar um bloco de código de sincronização sincronizado (this) de um objeto, outros threads serão impedidos de acessar todos os outros blocos de código de sincronização sincronizados (this) no objeto.
4. O terceiro exemplo também se aplica a outros blocos de código síncrono. Ou seja, quando um thread acessa um bloco de código de sincronização sincronizado (this) de um objeto, ele obtém a trava do objeto desse objeto. Como resultado, outros threads acesso a todas as partes do código síncrono do objeto objeto são temporariamente bloqueados.
5. As regras acima também se aplicam a outros bloqueios de objetos.
Simplificando, sincronizado declara uma fechadura para o encadeamento atual. O thread com esse bloqueio pode executar instruções no bloco e outros threads só podem aguardar o bloqueio para adquiri -lo antes da mesma operação.
Isso é muito útil, mas encontrei outra situação estranha.
1. Na mesma classe, existem dois métodos: usando a declaração de palavra -chave sincronizada
2. Ao executar um dos métodos, você precisa esperar o outro método (retorno de chamada de thread assíncrono) ser executado, para que você use um CountdownLatch para esperar.
3. O código é desconstruído da seguinte forma:
vazio sincronizado A () {CountdownLatch = new CountdownLatch (1); // faz algum pouco de contagemDownLatch.await ();} vazio sincronizado b () {Countdownlatch.CountDown ();} em
O método A é executado pelo encadeamento principal, o método B é executado pelo thread assíncrono e o resultado da execução de retorno de chamada é:
O thread principal começa a ficar preso depois de executar o método A, e não faz mais, e será inútil para você esperar, não importa quanto tempo leve.
Este é um problema clássico de impasse
A espera que B seja executado, mas, na verdade, não pense que B é um retorno de chamada, B também está esperando que um execute. Por que? O sincronizado desempenha um papel.
De um modo geral, quando queremos sincronizar um bloco de código, precisamos usar uma variável compartilhada para travá -la, por exemplo:
byte [] mutex = novo byte [0]; void a1 () {sincronizado (mutex) {// doSomething}} void b1 () {synchronized (mutex) {// doSomething}} Se o conteúdo do método A e B for migrado para os blocos sincronizados dos métodos A1 e B1, respectivamente, será fácil de entender.
Depois que o A1 for executado, ele aguardará indiretamente o método (Countdownlatch) B1 a ser executado.
No entanto, como o mutex no A1 não é liberado, começamos a esperar pelo B1. Neste momento, mesmo que o método de retorno de chamada assíncrono B1 precise aguardar a liberação do mutex, o método B não será executado.
Isso causou um impasse!
A palavra -chave sincronizada aqui é colocada na frente do método e a função é a mesma. Só que o idioma Java ajuda você a ocultar a declaração e o uso do mutex. O método sincronizado usado no mesmo objeto é o mesmo; portanto, mesmo um retorno de chamada assíncrono causará impulsos, então preste atenção a esse problema. Esse nível de erro é que a palavra -chave sincronizada é usada de maneira inadequada. Não o use aleatoriamente e use corretamente.
Então, o que exatamente é um objeto mutex tão invisível?
O exemplo em si é fácil de pensar. Porque dessa maneira, não há necessidade de definir um novo objeto e fazer uma trava. Para provar essa ideia, você pode escrever um programa para provar isso.
A ideia é muito simples. Defina uma classe e existem dois métodos. Um é declarado sincronizado e o outro é usado sincronizado (isso) no corpo do método. Em seguida, inicie dois threads para chamar esses dois métodos separadamente. Se ocorrer uma competição de bloqueio entre os dois métodos (espera), pode -se explicar que o mutex invisível em sincronizado declarado pelo método é realmente a própria instância.
classe pública multithreadsync {public sincronizado void m1 () lança interruptedException {System. out.println ("M1 Call"); Fio. Sleep (2000); Sistema. out.println ("M1 chamada feita"); } public void m2 () lança interruptedException {sincronizado (this) {System. out.println ("M2 Call"); Fio. Sleep (2000); Sistema. out.println ("M2 Call Done"); }} public static void main (string [] args) {final multithreadsync theObj = new MultithreadSync (); Thread t1 = new Thread () {@Override public void run () {try {thisobj.m1 (); } catch (interruptedException e) {e.printStackTrace (); }}}}; Thread t2 = new Thread () {@Override public void run () {try {thisobj.m2 (); } catch (interruptedException e) {e.printStackTrace (); }}}; t1.start (); t2.start (); }} A saída do resultado é:
M1 Callm1 Ligue
É explicado que o bloco de sincronização do método m2 está aguardando a execução do M1. Isso pode confirmar o conceito acima.
Deve-se notar que, quando a sincronização é adicionada ao método estático, pois é um método de nível de classe, o objeto bloqueado é a instância da classe da classe atual. Você também pode escrever um programa para provar isso. Aqui é omitido.
Portanto, a palavra -chave sincronizada do método pode ser substituída automaticamente por sincronizado (this) {} ao lê -lo, o que é fácil de entender.
Void Method () {Void Synchronized Method () {Sincronized (this) {// Biz Code // Biz Code} ------ >>>}} Visibilidade da memória de sincronizado
In Java, we all know that the keyword synchronized can be used to implement mutual exclusion between threads, but we often forget that it has another function, that is, to ensure the visibility of variables in memory - that is, when two threads read and write access to the same variable at the same time, synchronized is used to ensure that the write thread updates the variable, and the read thread can read the latest value of the variable when it accesses the variable again.
Por exemplo, o exemplo a seguir:
classe pública novidades {private estático booleano pronto = false; private static int número = 0; classe estática privada ReaderThread estende o thread {@Override public void run () {while (! Ready) {thread.yield (); // suporta a CPU para permitir que outros threads funcionem} System.out.println (número); }} public static void main (string [] args) {new ReaderThread (). start (); número = 42; pronto = true; }}O que você acha que os threads de leitura serão lançados? 42? Em circunstâncias normais, 42 serão emitidas. No entanto, devido a problemas de reordenação, o thread de leitura pode gerar nada ou não produzir nada.
Sabemos que o compilador pode reordenar o código ao compilar o código Java no bytecode, e a CPU também pode reordenar suas instruções ao executar instruções da máquina. Contanto que a reordenação não destrua a semântica do programa
Em um único thread, desde que a reordenação não afete o resultado da execução do programa, não se pode garantir que as operações nele sejam executadas na ordem especificada pelo programa, mesmo que a reordenação possa ter um impacto significativo em outros threads.
Isso significa que a execução da declaração "pronta = true" pode ter precedência sobre a execução da declaração "número = 42". Nesse caso, o encadeamento de leitura pode gerar o valor padrão do número 0.
Sob o modelo de memória Java, os problemas de reordenação levarão a esses problemas de visibilidade da memória. Sob o modelo de memória Java, cada encadeamento possui sua própria memória de trabalho (principalmente o cache ou registro da CPU), e suas operações nas variáveis são realizadas em sua própria memória de trabalho, enquanto a comunicação entre os threads é alcançada através da sincronização entre a memória principal e a memória de trabalho do encadeamento.
Por exemplo, para o exemplo acima, o thread de gravação atualizou o número para 42 e pronto para o verdadeiro, mas é muito provável que o thread de gravação sincronize apenas o número com a memória principal (talvez devido ao buffer de gravação da CPU), resultando em um valor pronto lido pelos threads de leitura subsequentes sempre sendo falsos, o código acima não produzirá nenhum valores numéricos.
Se usarmos a palavra -chave sincronizada para sincronizar, não haverá esse problema.
classe pública novidades {private estático booleano pronto = false; private static int número = 0; Lock de objeto estático privado = new Object (); classe estática privada ReaderThread estende o thread {@Override public void run () {Synchronized (Lock) {while (! Ready) {thread.yield (); } System.out.println (número); }} public static void main (string [] args) {sincronizado (bloqueio) {new ReaderThread (). start (); número = 42; pronto = true; }}} Isso ocorre porque o modelo de memória Java fornece as seguintes garantias para a semântica sincronizada.
Ou seja, quando a ThreadA libera o bloqueio m, as variáveis que ele escreveu (como x e y, que estão presentes em sua memória de trabalho) serão sincronizadas com a memória principal. Quando o ThreadB se aplicar para o mesmo bloqueio M, a memória de trabalho do ThreadB será definida como inválida e, em seguida, o ThreadB recarregará a variável que deseja acessar da memória principal em sua memória de trabalho (neste momento, x = 1, y = 1, é o valor mais recente modificado no ThreadA). Dessa forma, a comunicação entre threads de Threada para ThreadB é alcançada.
Esta é na verdade uma das regras que acontecem antes definidas pelo JSR133. O JSR133 define o seguinte conjunto de regras de acontecimento para o modelo de memória Java.
De fato, esse conjunto de regras ocorrer define a visibilidade da memória entre as operações. Se uma operação ocorrer antes da operação B, o resultado da execução de uma operação (como gravar para variáveis) deverá ser visível ao executar a operação B.
Para obter uma compreensão mais profunda dessas regras antes, vamos dar um exemplo:
// Código compartilhado por thread a e b lock = new object (); int a = 0; int b = 0; int c = 0; // Thread a, chame o seguinte código sincronizado (bloqueio) {a = 1; // 1 b = 2; // 2} // 3c = 3; // 4 // Thread B, chame o seguinte código sincronizado (bloqueio) {// 5 System.out.println (a); // 6 System.out.println (b); // 7 system.out.println (c); // 8}Assumimos que o encadeamento A execute primeiro, atribua valores às três variáveis A, B e C, respectivamente (Nota: A atribuição de variáveis A, B é realizada no bloco de declaração síncrona) e, em seguida, o Thread B é executado novamente, lendo os valores dessas três variáveis e imprimi -las. Então, quais são os valores das variáveis A, B e C impressos pelo Thread B?
De acordo com a regra de tiro único, na execução do encadeamento A, podemos obter que 1 operação ocorre antes de 2 operações, 2 operação ocorre antes de 3 operações e 3 operação ocorre antes de 4 operações. Da mesma forma, na execução do Thread B, 5 operações ocorre antes de 6 operações, 6 operações acontecem antes de 7 operações e 7 operações acontecem antes das 8 operações. De acordo com os princípios de desbloqueio e bloqueio do monitor, as três operações (operação de desbloqueio) acontecem antes das 5 operações (operação de travamento). De acordo com as regras transitivas, podemos concluir que as operações 1 e 2 acontecem antes das operações 6, 7 e 8.
De acordo com a semântica da memória de acontece antes, os resultados da execução das operações 1 e 2 são visíveis para as operações 6, 7 e 8; portanto, no encadeamento B, A e B são impressos devem ser 1 e 2. Para operações 4 e operação 8 da variável c. Não podemos deduzir a operação 4 antes da operação 8 de acordo com o existente acontece antes das regras. Portanto, no encadeamento B, a variável acessada a C ainda pode ser 0, não 3.