0. Código de perguntas pioneiras
O código a seguir demonstra um contador em que dois threads se acumulam, ao mesmo tempo, cada um realizando 1000.000 vezes. O resultado que esperamos é definitivamente i = 2000000. No entanto, depois de executá -lo muitas vezes, descobriremos que o valor de eu sempre serei menos de 2000000. Isso ocorre porque, quando dois threads escrevem I ao mesmo tempo, o resultado de um dos threads substituirá o outro.
classe pública AccountingSync implementa runnable {static int i = 0; public void aument () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {aumento (); }} public static void main (string [] args) lança interruptedException {AccountingSync AccountingSync = new AccountingSync (); Thread t1 = novo thread (AccountingSync); Thread T2 = novo thread (AccountingSync); t1.start (); t2.start (); t1.Join (); t2.Join (); System.out.println (i); }} Para resolver fundamentalmente esse problema, devemos garantir que vários threads sejam totalmente sincronizados ao operar i. Ou seja, quando o thread A grava i, o thread b não apenas não pode escrever, mas também não pode lê -lo.
1. O papel das palavras -chave sincronizadas
A função da palavra -chave sincronizada é realmente realizar a sincronização entre os threads. Seu trabalho é bloquear o código sincronizado, para que apenas um thread possa entrar no bloco de sincronização por vez, garantindo assim a segurança entre os threads. Assim como no código acima, a operação I ++ só pode ser executada por outro thread ao mesmo tempo.
2.Usagem de palavras -chave sincronizadas
Especifique bloqueio do objeto: bloqueie o objeto especificado, digite o bloco de código de sincronização para obter o bloqueio do objeto especificado
Atuando diretamente no método da instância: é equivalente a bloquear a instância atual. Digitando o bloco de código síncrono, você deve obter o bloqueio da instância atual (isso requer que, ao criar um thread, você deva usar a mesma instância executável)
Atuando diretamente sobre métodos estáticos: é equivalente a travar a classe atual. Antes de inserir o bloco de código síncrono, você deve obter o bloqueio da classe atual.
2.1 Especifique o objeto para travar
O código a seguir se aplica sincronizado a um determinado objeto. Há uma nota aqui que o objeto especificado deve ser estático; caso contrário, não compartilharemos o objeto cada vez que formos um thread, para que o significado do bloqueio não exista mais.
classe pública AccountingSync implementa Runnable {objeto estático final = new Object (); estático int i = 0; public void aument () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {sincronizado (object) {aumento (); }}} public static void main (string [] args) lança interruptedException {thread t1 = new Thread (new AccountingSync ()); Thread t2 = novo thread (new AccountingSync ()); t1.start (); t2.start (); t1.Join (); t2.Join (); System.out.println (i); }} 2.2 Aja diretamente no método da instância
A palavra -chave sincronizada age no método da instância, ou seja, antes de inserir o método aumente (), o encadeamento deve obter a trava da instância atual. Isso exige que usemos a mesma instância de objeto executável ao criar a instância do thread. Caso contrário, os bloqueios do encadeamento não estão na mesma instância; portanto, não há como falar sobre o problema de bloqueio/sincronização.
classe pública AccountingSync implementa runnable {static int i = 0; public sincronizado vazio aumentado () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {aumento (); }} public static void main (string [] args) lança interruptedException {AccountingSync AccountingSync = new AccountingSync (); Thread t1 = novo thread (AccountingSync); Thread T2 = novo thread (AccountingSync); t1.start (); t2.start (); t1.Join (); t2.Join (); System.out.println (i); }} Por favor, preste atenção às três primeiras linhas do método principal para ilustrar o uso correto das palavras -chave no método da instância.
2.3 agindo diretamente sobre métodos estáticos
Para aplicar a palavra -chave sincronizada ao método estático, não há necessidade de usar os dois threads para apontar para o mesmo método executável do exemplo acima. Como o bloco de métodos precisa solicitar o bloqueio da classe atual, não a instância atual, os threads ainda podem ser sincronizados corretamente.
classe pública AccountingSync implementa runnable {static int i = 0; public estático sincronizado vazio aumentado () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {aumento (); }} public static void main (string [] args) lança interruptedException {thread t1 = new Thread (new AccountingSync ()); Thread t2 = novo thread (new AccountingSync ()); t1.start (); t2.start (); t1.Join (); t2.Join (); System.out.println (i); }}3. Bloqueio incorreto
A partir do exemplo acima, sabemos que, se precisarmos de um balcão, para garantir a correção dos dados, naturalmente precisaremos bloquear o contador, para que possamos escrever o seguinte código:
classe pública BadlockonInteger implementa Runnable {Inteiro estático i = 0; @Override public void run () {for (int j = 0; j <1000000; j ++) {sincronizado (i) {i ++; }}} public static void main (string [] args) lança interruptedException {badlockoninteger badlockoninteger = new badlockoninteger (); Thread t1 = novo thread (badlockoninteger); Thread T2 = novo thread (BadlockonInteger); t1.start (); t2.start (); t1.Join (); t2.Join (); System.out.println (i); }}Quando executamos o código acima, descobriremos que a saída I é muito pequena. Isso significa que o tópico não é seguro.
Para explicar esse problema, precisamos começar com o número inteiro: em Java, o número inteiro é um objeto invariante. Como a string, uma vez que um objeto é criado, ele não pode ser modificado. Se você tem um número inteiro = 1, sempre será 1. E se você quiser este objeto = 2? Você só pode recriar um número inteiro. Após cada i ++, é equivalente a chamar o método de valores de número inteiro. Vamos dar uma olhada no código -fonte do método ValueOf do Inteiro:
public static integer valueof (int i) {if (i> = Integercache.low && i <= Integercache.high) Retorne Integercache.cache [i + (-integercache.low)]; devolver o novo número inteiro (i);} Integer.valueof () é na verdade um método de fábrica, que tende a retornar um novo objeto inteiro e copiar o valor para i;
Portanto, sabemos o motivo do problema. Como entre vários threads, como o I ++ vem depois de I aponte para um novo objeto, o thread pode carregar diferentes instâncias de objeto toda vez que estiver bloqueado. A solução é muito simples. Você pode resolvê -lo usando um dos três métodos de sincronizar acima.