1. Quando ocorrerão problemas de segurança de threads?
Não haverá problemas de segurança de threads em um único thread, mas em programação multithread, é possível acessar o mesmo recurso ao mesmo tempo. Esse recurso pode ser vários tipos de recursos: uma variável, um objeto, um arquivo, uma tabela de banco de dados, etc. Quando vários threads acessam o mesmo recurso ao mesmo tempo, haverá um problema:
Como o processo executado por cada encadeamento é incontrolável, é provável que o resultado final seja contrário ao desejo real ou levará diretamente a erros do programa.
Vamos dar um exemplo simples:
Agora, existem dois threads que leem os dados da rede separadamente e o inseram em uma tabela de banco de dados, exigindo que dados duplicados não possam ser inseridos.
Então deve haver duas operações no processo de inserção de dados:
1) Verifique se os dados existem no banco de dados;
2) Se houver, não será inserido; Se não existir, será inserido no banco de dados.
Se os dois threads forem representados por Thread-1 e Thread-2, respectivamente, e em algum momento, Thread-1 e Thread-2, ambos os dados leem X, isso pode acontecer:
O Thread-1 verifica se os dados x existem no banco de dados e o Thread-2 também verifica se os dados x existem no banco de dados.
Como resultado, o resultado da verificação dos dois threads é que os dados X não existem no banco de dados; portanto, os dois threads inserem dados x na tabela de banco de dados, respectivamente.
Este é um problema de segurança de threads, ou seja, quando vários threads acessam um recurso ao mesmo tempo, o resultado da execução do programa não é o resultado que você deseja ver.
Aqui, esse recurso é chamado: Recurso Crítico (também conhecido como recurso compartilhado).
Ou seja, quando vários threads acessam recursos críticos (um objeto, atributos em um objeto, um arquivo, um banco de dados etc.), os problemas de segurança do thread podem surgir.
No entanto, quando vários threads executam um método, as variáveis locais dentro do método não são recursos críticos, porque o método é executado na pilha, enquanto a pilha Java é privada de threads, portanto, não haverá problemas de segurança de threads.
2. Como resolver problemas de segurança de threads?
Então, em geral, como resolver problemas de segurança de threads?
Basicamente, ao resolver problemas de segurança de threads, todos os modos de simultaneidade adotam a solução "Acesso serializado a recursos críticos", ou seja, ao mesmo tempo, apenas um thread pode acessar recursos críticos, também conhecidos como acesso mutuamente exclusivo síncrono.
De um modo geral, um bloqueio é adicionado antes do código que acessa o recurso crítico. Depois de acessar o recurso crítico, o bloqueio é liberado e outros threads continuam a acessar.
Em Java, são fornecidas duas maneiras para implementar acesso síncrono mutex: sincronizado e bloquear.
Este artigo fala principalmente sobre o uso de sincronizado, e o uso do bloqueio é descrito na próxima postagem do blog.
Método de sincronização ou de sincronização sincronizado três.
Antes de entender o uso de palavras -chave sincronizadas, vamos primeiro olhar para um conceito: bloqueios mutex, como o nome sugere: bloqueios que podem alcançar o objetivo do acesso mutex.
Para dar um exemplo simples: se um recurso crítico for adicionado a um mutex, quando um thread acessa o recurso crítico, os outros threads só podem esperar.
Em Java, cada objeto possui uma marca de bloqueio (monitor), também conhecida como monitor. Quando vários threads acessam um objeto ao mesmo tempo, o thread só pode acessá -lo se adquirir a trava do objeto.
Em Java, a palavra -chave sincronizada pode ser usada para marcar um método ou bloco de código. Quando um encadeamento chama o método sincronizado do objeto ou acessa o bloco de código sincronizado, o encadeamento obtém o bloqueio do objeto. Outros tópicos não podem acessar o método por enquanto. Somente quando o método é executado ou o bloco de código é executado será o thread liberará o bloqueio do objeto e outros threads podem executar o método ou bloco de código.
A seguir, alguns exemplos simples para ilustrar o uso de palavras -chave sincronizadas:
1. Método sincronizado
No código a seguir, dois threads chamam o objeto InsertData para inserir dados:
public class Test {public static void main (string [] args) {final insertData insertData = new insertData (); novo thread () {public void run () {insertData.insert (thread.currentThread ()); }; }.começar(); novo thread () {public void run () {insertData.insert (thread.currentThread ()); }; }.começar(); }} classe insertData {private ArrayList <Teger> ArrayList = new ArrayList <Teger> (); public void insert (thread thread) {for (int i = 0; i <5; i ++) {System.out.println (thread.getName ()+"inserir dados"+i); ArrayList.add (i); }}} Neste momento, o resultado da saída do programa é:
Isso mostra que dois threads executam o método de inserção ao mesmo tempo.
Se a palavra -chave sincronizada for adicionada antes do método de inserção, o resultado da execução é:
classe insertData {private ArrayList <Teger> ArrayList = new ArrayList <Teger> (); public sincronizado void insert (thread) {for (int i = 0; i <5; i ++) {System.out.println (thread.getName ()+"inserir dados"+i); ArrayList.add (i); }}}A partir da saída acima, os dados inseridos no Thread-1 são executados apenas após a inserção do Thread-0. Isso mostra que os métodos de inserção de thread-0 e thread-1 executam sequencialmente.
Este é o método sincronizado.
No entanto, há alguns pontos a serem observados:
1) Quando um encadeamento está acessando o método sincronizado de um objeto, outros threads não podem acessar os outros métodos sincronizados do objeto. Esse motivo é muito simples, porque um objeto tem apenas um bloqueio. Quando um encadeamento adquire a trava do objeto, outros threads não podem obter a trava do objeto, para que eles não possam acessar outros métodos sincronizados do objeto.
2) Quando um encadeamento está acessando o método sincronizado de um objeto, outros threads podem acessar o método não sincronizado do objeto. A razão para isso é muito simples. O acesso a métodos não sincronizados não requer o bloqueio do objeto. Se um método não for modificado com a palavra -chave sincronizada, significa que não usará recursos críticos, outros threads podem acessar esse método.
3) Se um encadeamento A precisar acessar o método sincronizado FUN1 do objeto Object1, e outro encadeamento B precisará acessar o método sincronizado Fun1 do objeto Object2, mesmo que o objeto1 e o objeto2 sejam o mesmo tipo), não haverá um problema de segurança de thread, porque eles estão acessando objetos diferentes; portanto, não há problema de exclusão mútua.
2. Bloco de código sincronizado
Os blocos de código sincronizados são semelhantes ao seguinte formulário:
sincronizado (syncobject) {
}
Quando esse bloco de código é executado em um thread, o encadeamento adquire a trava do objeto SynObject, impossibilitando que outros threads acessem o bloco de código ao mesmo tempo.
O SynObject pode ser isso, representando o bloqueio que adquire o objeto atual, ou pode ser um atributo na classe, representando o bloqueio que adquire o atributo.
Por exemplo, o método de inserção acima pode ser alterado para os dois formulários a seguir:
classe insertData {private ArrayList <Teger> ArrayList = new ArrayList <Teger> (); public void insert (thread thread) {sincronizado (this) {for (int i = 0; i <100; i ++) {system.out.println (thread.getName ()+"inserir dados"+i); ArrayList.add (i); }}}} classe insertData {private ArrayList <Teger> ArrayList = new ArrayList <Teger> (); objeto privado objeto = new Object (); public void insert (thread thread) {sincronizado (object) {for (int i = 0; i <100; i ++) {System.out.println (thread.getName ()+"inserir dados"+i); ArrayList.add (i); }}}}Como pode ser visto no exposto, os blocos de código sincronizados são muito mais flexíveis de usar do que os métodos sincronizados. Como talvez apenas parte do código em um método precise ser sincronizada, se todo o método for sincronizado no momento, isso afetará a eficiência da execução do programa. Esse problema pode ser evitado usando blocos de código sincronizados. Os blocos de código sincronizado podem sincronizar apenas onde a sincronização é necessária.
Além disso, cada classe também possui um bloqueio, que pode ser usado para controlar o acesso simultâneo a membros de dados estáticos.
E se um thread executar o método sincronizado não estático de um objeto, e outro thread precisa executar o método sincronizado estático da classe à qual o objeto pertence, não haverá exclusão mútua neste momento, porque o acesso ao método sincronizado estático não há bloqueio de classe, enquanto não há acesso à exclusão não-estática.
Você entenderá olhando para o seguinte código:
public class Test {public static void main (string [] args) {final insertData insertData = new insertData (); novo thread () {@Override public void run () {insertData.insert (); } }.começar(); novo thread () {@Override public void run () {insertData.insert1 (); } }.começar(); }} classe insertData {public sincronizado void insert () {System.out.println ("Execute insert"); tente {thread.sleep (5000); } catch (interruptedException e) {e.printStackTrace (); } System.out.println ("Execute insert1"); } public sincronizado estático void insert1 () {System.out.println ("Execute insert1"); System.out.println ("Execute insert1"); }} Resultados de execução;
O método de inserção é executado no primeiro thread, que não fará com que o segundo encadeamento bloqueie o método Insert1.
Vamos dar uma olhada no que a palavra -chave sincronizada faz. Vamos descompilar seu bytecode. O bytecode descompilado do código a seguir é:
classe pública insertData {objeto privado = new Object (); public void insert (thread thread) {sincronizado (object) {}} public sincronizado void insert1 (thread thread) {} public void insert2 (thread) {}}No bytecode obtido por descompilação, pode -se observar que o bloco de código sincronizado realmente possui duas instruções: MonitorEnter e Monitorexit. Quando a instrução do MonitorEnter for executada, a contagem de bloqueio do objeto será aumentada em 1, enquanto quando a instrução Monitororexit for executada, a contagem de bloqueio do objeto será reduzida em 1. Na verdade, isso é muito semelhante à operação fotovoltaica no sistema operacional. A operação fotovoltaica no sistema operacional é usada para controlar vários threads para acessar recursos críticos. Para o método sincronizado, o encadeamento na execução reconhece se a estrutura Method_Info do método possui a configuração do sinalizador acc_synchronizada e, em seguida, adquire automaticamente o bloqueio do objeto, chama o método e finalmente libera o bloqueio. Se ocorrer uma exceção, o thread libera automaticamente o bloqueio.
Uma coisa a ser observada: para métodos sincronizados ou blocos de código sincronizado, quando ocorre uma exceção, a JVM liberará automaticamente o bloqueio ocupado pelo encadeamento atual, para que não haja impasse devido à exceção.
3. Algumas outras coisas notáveis sobre sincronizadas
1. A diferença entre sincronizado e sincronizado estático
Bloqueios sincronizados A instância atual da classe para impedir que outros threads acessem todos os blocos sincronizados da instância da classe ao mesmo tempo. Observe que esta é a "instância atual da classe" e não existe essa restrição em duas instâncias diferentes da classe. Em seguida, o sincronizado estático passa a controlar o acesso a todas as instâncias da classe. A sincronizada estática restringe os threads para acessar todas as instâncias da classe na JVM ao mesmo tempo e acessar o código correspondente rapidamente. De fato, se houver sincronizado em um método ou um bloco de código em uma classe, depois de gerar uma instância dessa classe, a classe terá um monitoramento rápido e o acesso simultâneo do thread à instância modificada é protegida rapidamente. O sincronizado estático é o público do monitoramento, que é a diferença entre os dois. Isto é, sincronizado é equivalente a isso.
Um autor japonês, o "Java Multithreaded Design Pattern", de Jie Chenghao, tem uma coluna como esta:
classe pulbic something () {public sincronizado void ISSynca () {} public sincronizado void ISSyncb () {} public static sincronizado void csyncA () {} public sincronizado estático void csyncb () {}}} Então, se duas instâncias A e B de algo são adicionadas, por que o seguinte grupo de métodos pode ser acessado simultaneamente por mais de um thread? axissynca () e x.issyncb ()
bxissynca () e y.issynca ()
CXCSYNCA () e Y.CSYNCB ()
dxissynca () e algo.csynca ()
Aqui, está claro que pode ser julgado:
A, tanto acesso ao domínio sincronizado da mesma instância, para que não possa ser acessado ao mesmo tempo. B é para diferentes instâncias, por isso pode ser acessado ao mesmo tempo. Por ser sincronizado estático, diferentes instâncias ainda serão restringidas, o que é equivalente a algo.ISSYNCA () e SOMGE.ISSYNCB (), para que não possa ser acessado ao mesmo tempo.
Então, e D?, A resposta no livro pode ser acessada simultaneamente. A razão para a resposta é que o Synchronzied é que o método da instância e o método da classe sincronizados são diferentes da trava.
A análise pessoal significa que sincronizado e sincronizado estático são equivalentes a duas gangues, cada uma com seu próprio controle, e não há restrições umas nas outras e podem ser acessadas ao mesmo tempo. Não está claro como o sincronziado é implementado no Java Internal Design.
Conclusão: A: estática sincronizada é o escopo de uma determinada classe. O csync estático sincronizado {} impede que vários threads acessem o método estático sincronizado nesta classe ao mesmo tempo. Funciona em todas as instâncias de objetos de uma classe.
B: Sincronizado é o escopo de uma instância. O ISSYNC () {} sincronizado impede que vários threads acessem o método sincronizado nessa instância ao mesmo tempo.
2. A diferença entre o método sincronizado e o código sincronizado rapidamente
Não há diferença entre os métodos sincronizados () {} e sincronizados (this) {}, mas os métodos sincronizados () {} são convenientes para a compreensão da leitura, enquanto sincronizados (this) {} podem controlar com mais precisão as áreas de acesso de conflitos e, às vezes, executar mais eficiente.
3. A palavra -chave sincronizada não pode ser herdada