Se sua fundação Java for fraca ou você não tiver um bom entendimento do Java Multithreading, leia este artigo "Aprenda a definição de threads, estado e propriedades do Java Multithreading"
A sincronização sempre foi um ponto difícil para o Java Multi-Threading, e raramente é usado quando estamos fazendo o desenvolvimento do Android, mas essa não é uma razão pela qual não estamos familiarizados com a sincronização. Espero que este artigo possa permitir que mais pessoas entendam e apliquem a sincronização de Java.
Em aplicativos multithread, dois ou mais threads precisam compartilhar acesso aos mesmos dados. Isso geralmente se torna uma condição de corrida se dois threads acessarem o mesmo objeto e cada encadeamento chama um método que modifica o objeto.
O exemplo mais fácil de condições de competição é: por exemplo, os ingressos para o trem são certos, mas há janelas para vender bilhetes de trem em todos os lugares, cada janela é equivalente a um tópico e muitos threads compartilham todo o recurso de ingresso para trem. E não pode garantir sua atomicidade. Se dois tópicos usarem esse recurso em um momento, os ingressos para o trem que eles retiram são os mesmos (os números dos assentos são os mesmos), o que causará problemas para os passageiros. A solução é que, quando um thread deseja usar o recurso de bilhete de trem, damos um bloqueio e, depois de terminar o trabalho, daremos o bloqueio a outro thread que deseja usar esse recurso. Dessa forma, a situação acima não ocorrerá.
1. Bloqueie o objeto
A palavra -chave sincronizada fornece automaticamente bloqueios e condições relacionadas. É muito conveniente usar sincronizado na maioria dos casos em que os bloqueios explícitos são necessários. No entanto, quando entendemos a classe Reentrantlock e objetos condicionais, podemos entender melhor a palavra -chave sincronizada. O Reentrantlock é introduzido no Java SE 5.0. A estrutura do bloco de código é protegida usando o ReentrantLock da seguinte forma:
mlock.lock (); tente {...} finalmente {mlock.unlock ();}Essa estrutura garante que apenas um encadeamento entre na área crítica a qualquer momento. Depois que um encadeamento bloqueia o objeto de bloqueio, nenhum outro encadeamento pode passar na instrução BLOCK. Quando outros threads chamam bloqueio, eles são bloqueados até que o primeiro thread libere o objeto de trava. É muito necessário colocar a operação de desbloqueio finalmente. Se ocorrer uma exceção na área crítica, o bloqueio deverá ser liberado; caso contrário, outros threads serão bloqueados para sempre.
2. Objeto condicional <Br /> Ao entrar na área crítica, verifica -se que ele só pode ser executado após a atendimento de uma certa condição. Use um objeto condicional para gerenciar threads que obtiveram um bloqueio, mas não podem fazer um trabalho útil. Objetos condicionais também são chamados de variáveis condicionais.
Vamos dar uma olhada no exemplo a seguir para ver por que objetos condicionais são necessários
Suponha que, em um cenário, precisamos usar transferências bancárias, primeiro escrevemos a classe bancária e seu construtor precisa ser transferido para o número de contas e o valor das contas.
Public Class Bank {Contas Double [] privado; Private Lock Banklock; Public Bank (int n, Double InitialBalance) {Accouts = new Double [n]; Banklock = new ReentrantLock (); for (int i = 0; i <conta.length; i ++) {contas [i] = InitialBalance; }}}Em seguida, queremos retirar dinheiro e escrever um método de retirada. De é o cedente, para o receptor e o valor da transferência de quantidade. Como resultado, descobrimos que o saldo do cedente é insuficiente. Se outros tópicos economizarem dinheiro suficiente para o cedente, a transferência poderá ser bem -sucedida. No entanto, este thread adquiriu o bloqueio, que é exclusivo, e outros threads não podem adquirir o bloqueio para executar operações de depósito. É por isso que precisamos introduzir objetos condicionais.
Public void Transfer (int de, int para, int) {Banklock.lock (); tente {while (contas [de] <valor) {// wait}} finalmente {Banklock.unlock (); }}Um objeto de bloqueio possui vários objetos de condição relacionada. Você pode usar o método New Condition para obter um objeto de condição. Depois de obtermos o objeto de condição, chamamos o método aguardando e o encadeamento atual é bloqueado e a fechadura é abandonada.
Public Class Bank {Contas Double [] privado; Private Lock Banklock; condição privada condição; Public Bank (int n, Double InitialBalance) {Accouts = new Double [n]; Banklock = new ReentrantLock (); // obtenha a condição do objeto condição = baklock.newcondition (); for (int i = 0; i <conta.length; i ++) {contas [i] = InitialBalance; }} public void transfer (int de, int para, int vale) lança interruptedException {Banklock.lock (); tente {while (contas [de] <valor) {// bloqueia o thread atual e desista da condição de bloqueio.await (); }} finalmente {Banklock.unlock (); }}} O thread aguardando o bloqueio é essencialmente diferente do thread que chama o método aguardando. Depois que um thread chama o método aguardando, ele entrará no conjunto de espera dessa condição. Quando o bloqueio está disponível, o encadeamento não pode desbloquear imediatamente, mas, em vez disso, está em um estado de bloqueio até que outro encadeamento chama o método SignalAll na mesma condição. Quando outro tópico estiver pronto para transferir dinheiro para o nosso cedente anterior, basta ligar para a condição.Signalall (); Esta chamada reativará todos os threads esperando por essa condição.
Quando um thread chama o método aguardando, ele não pode se reativar e espera que outros threads chamem o método SignalAll para se ativar. Se nenhum outro threads ativar o segmento de espera, o impasse ocorrerá. Se todos os outros threads estiverem bloqueados e as últimas chamadas de threads ativas aguardam antes de desbloquear outros threads, ele também será bloqueado. Nenhum thread pode desbloquear outros threads e o programa será suspenso.
Então, quando o sinalizador será chamado? Normalmente, deve ser benéfico chamar o SignalAll ao aguardar a direção da direção do thread. Neste exemplo, quando um saldo da conta muda, o tópico de espera deve ter a oportunidade de verificar o saldo.
Public void Transfer (int de, int para, int) lança interruptedException {Banklock.lock (); tente {while (contas [de] <valor) {// bloqueia o thread atual e desista da condição de bloqueio.await (); } // operação de transferência ... condicional.signalall (); } finalmente {Banklock.unlock (); }}Quando o método SignalAll é chamado, um encadeamento de espera não é ativado imediatamente. Simplesmente desbloqueia os threads de espera para que esses threads possam obter acesso ao objeto competindo depois que o thread atual sair do método síncrono. Outro método é o sinal, que desbloqueia aleatoriamente um thread. Se o thread ainda não puder ser executado, ele será bloqueado novamente. Se nenhum outro sinal chama o sinal novamente, o sistema será um impasse.
3. Palavras -chave sincronizadas
As interfaces de bloqueio e condição fornecem aos programadores um alto grau de controle de travamento; no entanto, na maioria dos casos, esse controle não é necessário e um mecanismo incorporado na linguagem Java pode ser usado. A partir do Java versão 1.0, todo objeto em Java tem um bloqueio interno. Se um método for declarado com a palavra -chave sincronizada, o bloqueio do objeto protegerá todo o método. Ou seja, para chamar esse método, o thread deve obter o bloqueio de objeto interno.
em outras palavras,
public sincronizado Void Method () {}Equivalente a
public void method () {this.lock.lock (); tente {} finalmente {this.lock.unlock ();} No exemplo do banco acima, podemos declarar o método de transferência da classe bancária como sincronizado em vez de usar um bloqueio exibido.
Existe apenas uma condição relacionada para o bloqueio de objeto interno. A esperança de espera é adicionada a um thread ao conjunto de espera. Notificar ou notificar os métodos desbloquear o tópico de espera. Em outras palavras, a espera é equivalente à condição de chamada.await (), o notifyAll é equivalente a condicionado.signalall ();
Nosso método de transferência de exemplo acima também pode ser escrito assim:
Public Sincronized Void Transfer (int de, int para, Int valor) lança interruptedException {while (contas [de] <valor) {wait (); } // Operação de transferência ... notifyAll (); }Você pode ver que o uso da palavra -chave sincronizado para escrever código é muito mais simples. Obviamente, para entender esse código, você deve entender que cada objeto possui um bloqueio interno e que o bloqueio tem uma condição interna. O bloqueio gerencia os threads que tentam inserir o método sincronizado e as condições gerenciam os threads que chamam de espera.
4. Bloqueio síncrono <r /> acima, dissemos que todo objeto Java tem uma trava, e um encadeamento pode chamar o método de sincronização para obter a trava, e há outro mecanismo para obter a trava. Ao entrar em um bloco de sincronização, quando o thread entra na seguinte forma de bloqueio:
sincronizado (obj) {}Então ele conseguiu a fechadura do OBJ. Vamos dar uma olhada na aula do banco
public class Bank {private duplo [] contas; private objeto bloqueio = new Object (); Public Bank (int n, Double InitialBalance) {Accouts = new Double [n]; for (int i = 0; i <conta.length; i ++) {contas [i] = InitialBalance; }} public void transfer (int de, int para, int vale) {Synchronized (Lock) {// Operação de transferência ...}}}Aqui, a criação de objetos de bloqueio é simplesmente usada para usar as fechaduras mantidas por cada objeto Java. Às vezes, os desenvolvedores usam o bloqueio de um objeto para implementar operações atômicas adicionais, chamadas de bloqueio do cliente. Por exemplo, a classe vetorial, seus métodos são síncronos. Agora suponha que o saldo bancário seja armazenado no vetor
Public void Transfer (Vector <double> contas, int de, int para, int vale) {contas.set (de, conta.get (de) -amount); conta.Set (para, conta.get (to)+vale;}Os métodos Get and Set da classe Vecror são síncronos, mas isso não nos ajudou. Após a conclusão da primeira chamada, é inteiramente possível que um tópico seja negado o direito de executar no método de transferência, para que outro tópico tenha armazenado valores diferentes no mesmo local de armazenamento, mas podemos interceptar esse bloqueio
Public void Transfer (Vector <double> contas, int de, int para, int vale) {sincronizado (contas) {conta.set (de, conta.get (de) -amount); Accouts.Set (para, conta.get (to)+valor;}}O bloqueio do cliente (blocos de código síncrono) é muito frágil e geralmente não é recomendado. Geralmente, é melhor usar as classes fornecidas no pacote java.util.concurrent, como bloquear filas. Se o método de sincronização for adequado para o seu programa, tente usar o método de sincronização. Pode reduzir o número de código escrito e reduzir a chance de erros. Se você precisar usar os recursos exclusivos fornecidos pela estrutura de bloqueio/condição, use apenas o bloqueio/condição.
O exposto acima é tudo sobre este artigo, espero que seja útil para o aprendizado de todos.