1. Uso básico de sincronizado
Sincronizado é o método mais usado para resolver problemas de simultaneidade em Java e o método mais fácil. O sincronizado possui três funções principais: (1) Verifique se o código de sincronização de acesso mutuamente exclusivo do encadeamento (2) garante que a modificação de variáveis compartilhadas possa ser visível em tempo hábil (3) resolver efetivamente o problema de reordenação. Sincronizado tem três usos de sincronizado:
(1) Método comum de modificar
(2) modificar métodos estáticos
(3) Modificar o bloco de código
Em seguida, usarei alguns programas de exemplo para ilustrar esses três métodos de uso (em prol da comparação, exceto pelos diferentes métodos de uso de sincronizados, os outros três códigos são basicamente consistentes).
1. Sem sincronização:
Snippet de código 1:
pacote com.paddx.test.concurrent; public class synchronizedtest {public void method1 () {System.out.println ("Método 1 start"); tente {System.out.println ("Método 1 execute"); Thread.sleep (3000); } catch (interruptedException e) {e.printStackTrace (); } System.out.println ("Método 1 final"); } public void method2 () {System.out.println ("Método 2 Start"); tente {System.out.println ("Método 2 execute"); Thread.sleep (1000); } catch (interruptedException e) {e.printStackTrace (); } System.out.println ("Método 2 final"); } public static void main (string [] args) {final sincronizado teste test = new SynchronizedTest (); novo thread (novo runnable () {@Override public void run () {test.method1 ();}}). start (); novo thread (novo runnable () {@Override public void run () {test.method2 ();}}). start (); }}O resultado da execução é o seguinte: Thread 1 e Thread 2 Digite o estado de execução ao mesmo tempo. O thread 2 é executado mais rápido que o Thread 1, então o Thread 2 é executado primeiro. Nesse processo, o Thread 1 e o Thread 2 executam ao mesmo tempo.
Método 1 Iniciar
Método 1 Execute
Método 2 Iniciar
Método 2 Execute
Método 2 final
Método 1 final
2. Sincronize métodos comuns:
Snippet de código dois:
pacote com.paddx.test.concurrent; public class SynchronizedTest {public Synchronized void Method1 () {System.out.println ("Método 1 Start"); tente {System.out.println ("Método 1 execute"); Thread.sleep (3000); } catch (interruptedException e) {e.printStackTrace (); } System.out.println ("Método 1 final"); } public sincronizado void method2 () {System.out.println ("Método 2 Start"); tente {System.out.println ("Método 2 execute"); Thread.sleep (1000); } catch (interruptedException e) {e.printStackTrace (); } System.out.println ("Método 2 final"); } public static void main (string [] args) {final sincronizado teste test = new SynchronizedTest (); novo thread (novo runnable () {@Override public void run () {test.method1 ();}}). start (); novo thread (novo runnable () {@Override public void run () {test.method2 ();}}). start (); }}O resultado da execução é o seguinte. Depois de compará -lo com o segmento de código, pode -se ver claramente que o Thread 2 precisa aguardar a execução do método1 do thread 1 para concluir antes de começar a executar o método Method2.
Método 1 Iniciar
Método 1 Execute
Método 1 final
Método 2 Iniciar
Método 2 Execute
Método 2 final
3. Método estático (classe) Sincronização
Snippet de código três:
pacote com.paddx.test.concurrent; classe pública SynchronizedTest {public static sincronizado void method1 () {System.out.println ("Método 1 start"); tente {System.out.println ("Método 1 execute"); Thread.sleep (3000); } catch (interruptedException e) {e.printStackTrace (); } System.out.println ("Método 1 final"); } public static sincronizado void method2 () {System.out.println ("Método 2 Start"); tente {System.out.println ("Método 2 execute"); Thread.sleep (1000); } catch (interruptedException e) {e.printStackTrace (); } System.out.println ("Método 2 final"); } public static void main (string [] args) {final sincronizado teste test = new SynchronizedTest (); Final SynchronizedTest test2 = new SynchronizedTest (); novo thread (novo runnable () {@Override public void run () {test.method1 ();}}). start (); novo thread (novo runnable () {@Override public void run () {test2.method2 ();}}). start (); }}O resultado da execução é o seguinte. A sincronização dos métodos estáticos é essencialmente uma sincronização de classes (os métodos estáticos são essencialmente métodos de classe, não métodos em objetos). Portanto, mesmo que o teste e o teste2 pertencem a diferentes objetos, ambos pertencem a instâncias da classe sincronizada, portanto, o método1 e o método2 só podem ser executados sequencialmente e não podem ser executados simultaneamente.
Método 1 Iniciar
Método 1 Execute
Método 1 final
Método 2 Iniciar
Método 2 Execute
Método 2 final
4. Sincronização de bloco de código
Snippet de código quatro:
pacote com.paddx.test.concurrent; public class synchronizedtest {public void method1 () {System.out.println ("Método 1 start"); tente {sincronizado (this) {System.out.println ("Método 1 Execute"); Thread.sleep (3000); }} catch (interruptEdException e) {e.printStackTrace (); } System.out.println ("Método 1 final"); } public void method2 () {System.out.println ("Método 2 Start"); tente {sincronizado (this) {System.out.println ("Método 2 Execute"); Thread.sleep (1000); }} catch (interruptEdException e) {e.printStackTrace (); } System.out.println ("Método 2 final"); } public static void main (string [] args) {final sincronizado teste test = new SynchronizedTest (); novo thread (novo runnable () {@Override public void run () {test.method1 ();}}). start (); novo thread (novo runnable () {@Override public void run () {test.method2 ();}}). start (); }}O resultado da execução é o seguinte. Embora o thread 1 e o thread 2 digite o método correspondente e inicie a execução, o Thread 2 precisa aguardar a execução do bloco de sincronização no thread 1 para concluir antes de entrar no bloco de sincronização.
Método 1 Iniciar
Método 1 Execute
Método 2 Iniciar
Método 1 final
Método 2 Execute
Método 2 final
2. Princípio sincronizado
Se você ainda tiver alguma dúvida sobre os resultados da execução acima, não se preocupe. Vamos primeiro entender o princípio do sincronizado e depois olhar para as perguntas acima para ver de relance. Vejamos primeiro como o código sincronizado sincroniza os blocos de código descompilando o seguinte código:
pacote com.paddx.test.concurrent; public class synchronizeddemo {public void method () {synchronized (this) {System.out.println ("Método 1 iniciar"); }}}Resultado da decompilação:
Em relação ao papel dessas duas instruções, nos referimos diretamente à descrição na especificação da JVM:
Monitorente:
Cada objeto está associado a um monitor. Um monitor está bloqueado se e somente se tiver um proprietário. O encadeamento que executa o MonitorEnter tenta obter a propriedade do monitor associado ao Objectref, como segue: • Se a contagem de entrada do monitor associada ao Objectref for zero, o encadeamento entra no monitor e define sua contagem de entrada para um. O thread é então o proprietário do monitor. • Se o thread já proprietário do monitor associado ao Objectref, ele reentiva o monitor, aumentando sua contagem de entrada. • Se outro thread já possui o monitor associado ao Objection, o encadeamento blocos até que a contagem de entrada do monitor seja zero, tenta novamente ganhar a propriedade.
O significado geral desta passagem é:
Cada objeto possui uma trava de monitor (monitor). Quando o monitor estiver ocupado, ele será bloqueado. Quando o thread executa a instrução do MonitorEnter, ele tenta obter a propriedade do monitor. O processo é o seguinte:
1. Se o número de entrada do monitor for 0, o encadeamento entra no monitor e define o número de entrada como 1, o thread é o proprietário do monitor.
2. Se o thread já possui o monitor e apenas reentre, o número de entrada no monitor será adicionado a 1.
3. Se outros threads ocuparam o monitor, o encadeamento entrará em um estado de bloqueio até que o número de entrada do monitor seja 0 e tente obter a propriedade do monitor novamente.
monitorexit:
O encadeamento que executa o monitorexit deve ser o proprietário do monitor associado à instância referenciada pelo objectref. O encadeamento diminui a contagem de entrada do monitor associado ao objectref. Se, como resultado, o valor da contagem de entrada for zero, o encadeamento sai do monitor e não é mais seu proprietário. Outros tópicos que estão bloqueando para entrar no monitor podem tentar fazê -lo.
O significado geral desta passagem é:
A execução do encadeamento Monitororexit deve ser o proprietário do monitor correspondente ao Objectref.
Quando a instrução é executada, o número de entrada do monitor é reduzido em 1. Se o número de entrada do monitor for 0 após o decréscimo em 1, o encadeamento sairá do monitor e não for mais o proprietário deste monitor. Outros tópicos bloqueados por este monitor podem tentar obter a propriedade deste monitor.
Através desses dois parágrafos de descrição, devemos ser capazes de ver claramente o princípio da implementação do sincronizado. A camada semântica subjacente de sincronizada é concluída através de um objeto de monitor. De fato, aguarde/notificar e outros métodos também dependem de objetos de monitor. É por isso que apenas métodos como espera/notificação podem ser chamados em blocos ou métodos sincronizados, caso contrário, uma exceção de java.lang.illegalmonitorStateException será lançada.
Vejamos os resultados da decompilação do método de sincronização:
código -fonte:
pacote com.paddx.test.concurrent; public class synchronizedmethod {public sincronizado void method () {System.out.println ("Hello World!"); }}Resultado da decompilação:
A julgar pelos resultados da decompilação, a sincronização do método não é concluída através do monitorador de instruções e do monitorexit (em teoria, ele também pode ser implementado nessas duas instruções). No entanto, em comparação com os métodos comuns, o identificador acc_synchronized é adicionado ao seu pool constante. A JVM implementa a sincronização de métodos com base nesse identificador: quando o método é chamado, a instrução de chamada verificará se o sinalizador de acesso acc_synchronizado do método está definido. Se definido, o encadeamento de execução obterá primeiro o monitor e executará o corpo do método após a execução do método. Depois que o método for executado, o monitor será liberado. Durante a execução do método, nenhum outro thread pode mais obter o mesmo objeto de monitor. De fato, não há diferença em essência, mas a sincronização do método é uma maneira implícita de alcançá -lo sem a necessidade de ser feita através do bytecode.
3. Explicação dos resultados da operação
Com um entendimento do princípio do sincronizado, você pode resolvê -lo facilmente olhando para o programa acima.
1. Segmento de código 2 Resultados:
Embora o método1 e o método2 sejam métodos diferentes, ambos os métodos são sincronizados e são chamados através do mesmo objeto. Portanto, antes de ligar, você precisa competir pelo bloqueio (monitor) no mesmo objeto, para obter apenas os bloqueios mutuamente exclusivamente. Portanto, o método1 e o método2 só podem ser executados sequencialmente.
2. Segmento de código 3 Resultados:
Embora o teste e o teste2 pertencem a diferentes objetos, o teste e o teste2 pertencem a diferentes instâncias da mesma classe. Como o método1 e o método2 pertencem a métodos de sincronização estática, você precisa obter o monitor na mesma classe (cada classe corresponde apenas a um objeto de classe), para que você possa executar apenas sequencialmente.
3. Segmento de código 4 Resultados:
Para a sincronização dos blocos de código, é essencialmente necessário obter o monitor do objeto entre colchetes após a palavra -chave sincronizada. Como o conteúdo dos colchetes neste código é esse, e o método1 e o método2 são chamados através do mesmo objeto; portanto, antes de inserir o bloco de sincronização, você precisa competir pelos bloqueios no mesmo objeto, para que o bloco de sincronização só possa ser executado em sequência.
Quatro resumo
O sincronizado é o método mais usado para a segurança do encadeamento na programação simultânea Java e é relativamente simples de usar. No entanto, se pudermos entender seus princípios em profundidade e ter algum entendimento dos conhecimentos subjacentes, como bloqueios de monitor, ele pode nos ajudar a usar corretamente as palavras -chave sincronizadas e, por outro lado, também pode nos ajudar a entender melhor o mecanismo de programação de concorrência, ajude -nos a escolher melhores estratégias de concorrência para concluir as tarefas diferentes. Você também pode lidar calmamente com vários problemas simultâneos que encontra na vida cotidiana.