Este artigo segue principalmente dois artigos anteriores de multi-threading para resumir questões de segurança de threads no Java multi-threading.
1. Um exemplo típico de segurança do tópico Java
public class ThreadTest {public static void main (string [] args) {conta de conta = new Account ("123456", 1000); DrawMoneyRunnable drawMoneyRunnable = new DrawMoneyRunnable (conta, 700); Thread mythread1 = novo thread (drawmoneyrunnable); Thread mythread2 = novo thread (drawmoneyrunnable); mythread1.start (); mythread2.start (); }} classe DrawMoneyRunnable implementa Runnable {Conta de Conta Private; Duasidade dupla privada; public drawMoneyRunnable (conta de conta, drawAdAdAut) {super (); this.account = conta; this.drawamount = drawAdount; } public void run () {if (account.getBalance ()> = drawAdount) {// 1 System.out.println ("A retirada foi bem -sucedida, a retirada do dinheiro é:" + drawAdamount); Balance duplo = account.getBalance () - drawAdAut; conta.setBalance (saldo); System.out.println ("Balance é:" + Balance); }}} classe de classe {private string AccountNo; equilíbrio duplo privado; public conta () {} public conta (string accountNo, duplo saldo) {this.AccountNo = AccountNo; this.Balance = Balance; } public string getAccountno () {return AccountNo; } public void setAccountNo (String AccountNo) {this.AccountNo = AccountNo; } public Double getBalance () {return Balance; } public void SetBalance (Balance duplo) {this.Balance = Balance; }}O exemplo acima é fácil de entender. Há um cartão bancário com um saldo de 1.000. O programa simula a cena em que você e sua esposa estão retirando dinheiro no caixa eletrônico ao mesmo tempo. Execute este programa várias vezes e pode ter resultados de saída em várias combinações diferentes. Uma das saídas possíveis é:
1 A retirada do dinheiro é bem -sucedida, a retirada do dinheiro é: 700.0
2 O saldo é: 300.0
3 A retirada do dinheiro é bem -sucedida, a retirada do dinheiro é: 700.0
4 O saldo é: -400.0
Em outras palavras, para um cartão bancário com um saldo de apenas 1.000, você pode retirar um total de 1.400, o que é obviamente um problema.
Após a análise, o problema reside na incerteza da execução em um ambiente multi-thread Java. A CPU pode alternar aleatoriamente entre vários threads no estado pronto; portanto, é muito provável que a seguinte situação ocorra: Quando o Thread1 executa o código em // 1, a condição de julgamento é verdadeira. Neste momento, a CPU muda para Thread2, executa o código em // 1 e descobre que ainda é verdade. Em seguida, o Thread2 é executado e depois alterne para Thread1 e a execução é concluída. Neste momento, os resultados acima aparecerão.
Portanto, quando se trata de problemas de segurança de threads, na verdade significa que o acesso a recursos compartilhados em um ambiente multithread pode causar inconsistência nesse recurso compartilhado. Portanto, para evitar problemas de segurança de threads, o acesso simultâneo a esse recurso compartilhado em um ambiente multithread deve ser evitado.
2. Método de sincronização
A modificação de palavras -chave sincronizadas é adicionada à definição do método para acessar recursos compartilhados, tornando esse método chamado método de sincronização. Pode -se simplesmente entender que esse método está bloqueado e seu objeto trancado é o próprio objeto onde o método atual está localizado. Em um ambiente multithread, ao executar esse método, você deve primeiro obter esse bloqueio de sincronização (e no máximo apenas um thread pode obtê-lo). Somente quando o thread executar esse método de sincronização será liberado, e outros threads podem obter esse bloqueio de sincronização e assim por diante ...
No exemplo acima, o recurso compartilhado é um objeto de conta e, ao usar o método de sincronização, ele pode resolver problemas de segurança do encadeamento. Basta adicionar a palavra -chave synshronizada antes do método run ().
public sincronizado void run () {// ....}3. Sincronizar blocos de código
Conforme analisado acima, a resolução de problemas de segurança de threads requer apenas limitar a incerteza do acesso a recursos compartilhados. Ao usar o método de sincronização, todo o corpo do método se torna um estado de execução síncrono, o que pode causar o intervalo de sincronização. Portanto, outro método de sincronização - o bloco de código de sincronização - pode ser resolvido diretamente para o código que precisa de sincronização.
O formato do bloco de código síncrono é:
sincronizado (obj) {// ...}Entre eles, OBJ é o objeto de trava, por isso é crucial escolher qual objeto ser bloqueado. De um modo geral, esse objeto de recurso compartilhado é selecionado como o objeto de bloqueio.
Como no exemplo acima, é melhor usar o objeto de conta como objeto de bloqueio. (Obviamente, também é possível escolher isso, porque o segmento de criação usa o método executável. Se for um thread criado diretamente herdando o método do encadeamento, usando esse objeto como um bloqueio de sincronização, na verdade, não desempenhará nenhum papel porque é um objeto diferente. Portanto, você precisa ter cuidado extra ao escolher um bloqueio de sincronização ...)
4. Lock Object Syncronization Lock
Como podemos ver acima, precisamente porque precisamos ter muito cuidado com a seleção de objetos de bloqueio síncrono, existe alguma solução simples? Ele pode facilitar a dissociação de objetos de bloqueio síncronos de recursos compartilhados, além de resolver bem os problemas de segurança dos threads.
O uso de bloqueios de sincronização do objeto de bloqueio pode resolver facilmente esse problema. A única coisa a observar é que o objeto de bloqueio precisa ter um relacionamento individual com o objeto de recursos. O formato geral do bloqueio de sincronização do objeto de trava é:
Classe X {// Exibe o objeto que define o bloqueio de sincronização de bloqueio, que possui um relacionamento individual com o recurso compartilhado Lock final de bloqueio final = new reentrantlock (); public void M () {// Lock.lock (); // ... código que requer sincronização segura de Thread // libere o bloqueio de trava.unlock (); }}5.wait ()/notify ()/notifyall () comunicação de thread
Esses três métodos são mencionados na postagem do blog "Java Resumo Série: java.lang.object". Embora esses três métodos sejam usados principalmente no multithreading, eles são na verdade métodos locais na classe de objeto. Portanto, teoricamente, qualquer objeto pode ser usado como o tom principal desses três métodos. Na programação real de threading, apenas sincronizando o objeto de bloqueio para ajustar esses três métodos pode ser concluído a comunicação entre vários threads.
espera (): faz com que o thread atual espere e faça com que ele entre em um estado de bloqueio de espera. Até que outro encadeamento chama o método notify () ou notifyAll () do objeto de bloqueio síncrono para acordar o thread.
notify (): acorde um único thread aguardando neste objeto de bloqueio síncrono. Se vários threads estiverem aguardando esse objeto de bloqueio síncrono, um dos threads será selecionado para operação de despertar. Somente quando o encadeamento atual abandona o bloqueio no objeto de bloqueio síncrono, o thread despertado pode ser executado.
notifyall (): acorde todos os threads aguardando neste objeto de bloqueio síncrono. Somente quando o encadeamento atual abandona o bloqueio no objeto de bloqueio síncrono, o thread despertado pode ser executado.
pacote com.qqyumidi; public class ThreadTest {public static void main (string [] args) {conta de conta = new Account ("123456", 0); Thread drawMoneyThread = new DrawMoneyThread ("Get Money Thread", conta, 700); Thread DepositMoneyThread = new DepositMoneyThread ("Salve Money Thread", Conta, 700); drawMoneyThread.start (); depositMoneyThread.start (); }} classe drawMoneyThread estende o thread {conta privada; quantia dupla privada; public drawMoneyThread (string threadName, conta de conta, valor duplo) {super (threadName); this.account = conta; this.amount = valor; } public void run () {for (int i = 0; i <100; i ++) {conta.draw (quantidade, i); }}} classe depositemoneyThread estende o thread {conta privada; quantia dupla privada; public DepositMoneyThread (string threadName, conta de conta, valor duplo) {super (threadName); this.account = conta; this.amount = valor; } public void run () {for (int i = 0; i <100; i ++) {conta.deposit (quantidade, i); }}} classe de classe {private string AccountNo; equilíbrio duplo privado; // Identifique se já existe um depósito na conta da bandeira booleana privada = false; public conta () {} public conta (string accountNo, duplo saldo) {this.AccountNo = AccountNo; this.Balance = Balance; } public string getAccountno () {return AccountNo; } public void setAccountNo (String AccountNo) {this.AccountNo = AccountNo; } public Double getBalance () {return Balance; } public void SetBalance (Balance duplo) {this.Balance = Balance; } / ** * Economize dinheiro * * @param depositamount * / public sincronizado depósito de vazio (depósito duplo, int i) {if (flag) {// alguém na conta já economizou dinheiro e o thread atual precisa para bloquear o {system.out.println (thread.currentThread (). espere(); // 1 System.out.println (Thread.currentThread (). GetName () + "Operação de espera executada" + " - i =" + i); } catch (interruptedException e) {e.printStackTrace (); }} else {// comece a salvar o sistema.out.println (thread.currentThread (). getName () + "depósito:" + depositamount + " - i =" + i); SetBalance (Balance + Depositamount); bandeira = true; // acorde outros threads notifyAll (); // 2 tente {thread.sleep (3000); } catch (interruptedException e) {e.printStackTrace (); } System.out.println (thread.currentThread (). GetName () + "- economize dinheiro- a execução é concluída" + "- i =" + i); }} / ** * Retire o dinheiro * * @param drawAdAdamount * / public sincronizado void draw (drawamount duplo, int i) {if (! Flag) {// Nenhuma conta na conta economizou dinheiro ainda, e o segmento atual precisará para bloquear {System.out.println (thread.UrtRentThread (). i = " + i); espere(); System.out.println (thread.currentThread (). GetName () + "execute a operação de espera" + "execute operação de espera" + " - i =" + i); } catch (interruptedException e) {e.printStackTrace (); }} else {// comece a retirar o dinheiro System.out.println (thread.currentThread (). getName () + "retirar dinheiro:" + drawAdount + " - i =" + i); setBalance (getBalance () - drawAdAut); bandeira = false; // acorde outros threads notifyAll (); System.out.println (thread.currentThread (). GetName () + "-retirar dinheiro-a execução é concluída" + "-i =" + i); // 3}}} O exemplo acima demonstra o uso de wait ()/notify ()/notifyAll (). Alguns resultados de saída são:
O fio de retirada de dinheiro começa a executar a operação de espera e executar a operação de espera- i = 0
Salvando depósito de threads: 700.0 - i = 0
Economize dinheiro com execução de dinheiro de threads-i = 0
O tópico de economia de dinheiro precisa executar a operação de espera- i = 1
O encadeamento de retirada de dinheiro executa operação de espera e espera- i = 0
Retire o fio do dinheiro Retira dinheiro: 700.0 - i = 1
Frea de retirada de dinheiro--Tithdrawal-Execução-I = 1
O tópico para retirar dinheiro deve começar a executar a operação de espera e executar a operação de espera- i = 2
O encadeamento de economia de dinheiro executa a operação de espera- i = 1
Salvando depósito de threads: 700.0 - i = 2
Economize dinheiro com EXECUÇÃO DINHEIRO DINHEIRO-I = 2
O fio de retirada executa a operação de espera e executa a operação de espera- i = 2
Retirar o tópico do dinheiro retirado dinheiro: 700.0 - i = 3
Frea de retirada de dinheiro--Tithdrawal-Execução-I = 3
O tópico para retirar dinheiro deve executar a operação de espera e executar a operação de espera- i = 4
Salvando depósito de threads: 700.0 - i = 3
Economize dinheiro com EXECUÇÃO DINHEIRO DINHEIRA-I = 3
O tópico de economia de dinheiro precisa executar a operação de espera- i = 4
O tópico de retirada de dinheiro executa a operação de espera e espera- i = 4
Retire o tópico do dinheiro retirado dinheiro: 700.0 - i = 5
Frea de retirada de dinheiro----Execução-I = 5
O tópico para retirar dinheiro deve começar a executar a operação de espera e executar a operação de espera- i = 6
O encadeamento de economia de dinheiro executa a operação de espera- i = 4
Salvando depósito de threads: 700.0 - i = 5
Economize dinheiro com EXECUÇÃO DINHEIRO DINHEIRA-I = 5
O tópico de economia de dinheiro precisa executar a operação de espera- i = 6
O encadeamento de retirada de dinheiro executa operação de espera e espera- i = 6
Retire o tópico do dinheiro retirado dinheiro: 700.0 - i = 7
Frea de retirada de dinheiro--Tithdrawal-Execução-I = 7
O fio de retirada de dinheiro começa a executar a operação de espera e executar a operação de espera-I = 8
O encadeamento de economia de dinheiro executa a operação de espera- i = 6
Salvando depósito de threads: 700.0 - i = 7
Portanto, precisamos prestar atenção aos seguintes pontos:
1. Depois que o método Wait () é executado, o encadeamento atual entra imediatamente no estado de bloqueio de espera e o código subsequente não será executado;
2. Após a execução do método notify ()/notifyAll (), o objeto Thread (anynotify ()/all-notifyAll ()) nesse objeto de bloqueio de sincronização será despertado. No entanto, o objeto de bloqueio de sincronização não é liberado neste momento. Ou seja, se houver código por trás do notify ()/notifyAll (), continuará a prosseguir. Somente quando o encadeamento atual é executado, o objeto de bloqueio de sincronização será liberado;
3. Após o notificação ()/notifyAll (), é executado, se houver um método Sleep () à direita, o encadeamento atual entrará em um estado de bloqueio, mas o bloqueio do objeto de sincronização não será liberado e ainda será retido por si só. Em seguida, o tópico continuará sendo executado após um certo período de tempo, os próximos 2;
4. Wait ()/notify ()/nitifyAll () completa comunicação ou colaboração entre threads com base em diferentes bloqueios de objetos. Portanto, se for um bloqueio de objeto de sincronização diferente, perderá seu significado. Ao mesmo tempo, o bloqueio do objeto de sincronização é melhor manter uma correspondência individual com o objeto de recurso compartilhado;
5. Quando o Thread Wait acorda e executa, o código do método wait () que foi executado na última vez continua sendo executado.
Obviamente, o exemplo acima é relativamente simples, apenas para simplesmente usar o método wait ()/notify ()/noitifyAll (), mas, em essência, já é um modelo simples do produtor-consumidor.
Série de artigos:
Explicação das instâncias multi-threaded java (i)
Explicação detalhada das instâncias multi-thread Java (ii)
Explicação detalhada das instâncias multi-threaded Java (iii)