Você notou Race Condition no exemplo de simultaneidade multi-threading do Java? Como lidar com isso?
Publicados: 2019-12-11 Algum tempo atrás eu escrevi um artigo sobre o exemplo do consumidor do produtor e como lidar melhor com a operação de leitura/gravação em Java. Na mesma nota, neste tutorial discutiremos algo sobre Race Condition
e Thread locking
.
Se você tem alguma das perguntas abaixo, então você está no lugar certo:
- Exemplo de condição de corrida Java
- exemplo java mutex
- multithreading – O que é uma condição de corrida?
- Condições de Corrida e Seções Críticas
- O que é condição de corrida?
- Como lidar com a condição de corrida em Java com exemplo
Por que a condição de corrida em Java ocorre?
A condição de corrida em Java ocorre quando two or more threads
tentam modificar/atualizar dados compartilhados ao same time
.
Vamos dar uma olhada na lógica do programa abaixo:
Este é um exemplo bancário muito simples no qual você
deposit
ewithdraw
quantias100 times
. Você depositará $ 100 no total 100 vezes = $ 100 x 100 = $ 10.000 e retirará $ 50 no total 100 vezes = $ 50 x 100 = $ 5.000. No final da conclusão do programa, você deve ter $ 5.000 em seu banco.
Aqui estão os passos:
- Criar classe CrunchifyRaceCondition.java
- Criar classe CrunchifyTransaction.java
- Criar classe CrunchifyBankAccount.java
- Executaremos a classe CrunchifyRaceCondition e ela iniciará o loop de depósito e retirada 100 vezes.
- Vamos executá-lo
with Synchronized block
para verificar o resultado - Vamos executá-lo
without Synchronized block
para verificar o resultado
CrunchifyRaceCondition.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package com . crunchify . tutorial ; /** * @author Crunchify.com * */ public class CrunchifyRaceCondition { public static void main ( String [ ] args ) { CrunchifyBankAccount crunchifyAccount = new CrunchifyBankAccount ( "CrunchifyAccountNumber" ) ; // Total Expected Deposit: 10000 (100 x 100) for ( int i = 0 ; i < 100 ; i ++ ) { CrunchifyTransaction t = new CrunchifyTransaction ( crunchifyAccount , CrunchifyTransaction . TransactionType . DEPOSIT_MONEY , 100 ) ; t . start ( ) ; } // Total Expected Withdrawal: 5000 (100 x 50) for ( int i = 0 ; i < 100 ; i ++ ) { CrunchifyTransaction t = new CrunchifyTransaction ( crunchifyAccount , CrunchifyTransaction . TransactionType . WITHDRAW_MONEY , 50 ) ; t . start ( ) ; } // Let's just wait for a second to make sure all thread execution completes. try { Thread . sleep ( 1000 ) ; } catch ( InterruptedException e ) { System . out . println ( e ) ; } // Expected account balance is 5000 System . out . println ( "Final Account Balance: " + crunchifyAccount . getAccountBalance ( ) ) ; } } |
CrunchifyTransaction.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package com . crunchify . tutorial ; /** * @author Crunchify.com */ class CrunchifyTransaction extends Thread { public static enum TransactionType { DEPOSIT_MONEY ( 1 ) , WITHDRAW_MONEY ( 2 ) ; private TransactionType ( int value ) { } } ; private TransactionType transactionType ; private CrunchifyBankAccount crunchifyAccount ; private double crunchifyAmount ; /* * If transactionType == 1, depositAmount() else if transactionType == 2 withdrawAmount() */ public CrunchifyTransaction ( CrunchifyBankAccount crunchifyAccount , TransactionType transactionType , double crunchifyAmount ) { this . transactionType = transactionType ; this . crunchifyAccount = crunchifyAccount ; this . crunchifyAmount = crunchifyAmount ; } public void run ( ) { switch ( this . transactionType ) { case DEPOSIT_MONEY : depositAmount ( ) ; printBalance ( ) ; break ; case WITHDRAW_MONEY : withdrawAmount ( ) ; printBalance ( ) ; break ; default : System . out . println ( "NOT A VALID TRANSACTION" ) ; } } public void depositAmount ( ) { this . crunchifyAccount . depositAmount ( this . crunchifyAmount ) ; } public void withdrawAmount ( ) { this . crunchifyAccount . withdrawAmount ( crunchifyAmount ) ; } public void printBalance ( ) { System . out . println ( Thread . currentThread ( ) . getName ( ) + " : TransactionType: " + this . transactionType + ", Amount: " + this . crunchifyAmount ) ; System . out . println ( "New Account Balance: " + this . crunchifyAccount . getAccountBalance ( ) ) ; } } |

CrunchifyBankAccount.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
package com . crunchify . tutorial ; /** * @author Crunchify.com */ class CrunchifyBankAccount { private String crunchifyAccountNumber ; private double crunchifyAccountBalance ; public String getAccountNumber ( ) { return crunchifyAccountNumber ; } public double getAccountBalance ( ) { return crunchifyAccountBalance ; } public CrunchifyBankAccount ( String crunchifyAccountNumber ) { this . crunchifyAccountNumber = crunchifyAccountNumber ; } // Make a note of this line -- synchronized keyword added public synchronized boolean depositAmount ( double amount ) { if ( amount < 0 ) { return false ; } else { crunchifyAccountBalance = crunchifyAccountBalance + amount ; return true ; } } // Make a note of this line -- synchronized keyword added public synchronized boolean withdrawAmount ( double amount ) { if ( amount > crunchifyAccountBalance ) { return false ; } else { crunchifyAccountBalance = crunchifyAccountBalance - amount ; return true ; } } } |
Verifique as linhas 24 e 34 acima. Mantenha essa palavra-chave Synchronized
e execute seu programa. Você deve ver o resultado correto como você vê na imagem abaixo.
Agora remova a palavra-chave sincronizada das linhas 24 e 34 e execute o mesmo programa.
Pode ser necessário executar este programa várias vezes para ver um problema. Em java, não há garantia de que você verá condição de corrida o tempo todo.
Se você tem um aplicativo de nível empresarial e está falando de milhões de transações por segundo, a condição de corrida pode causar um desastre para sua empresa.
Agora a pergunta é como evitar Race Condition em seu aplicativo Java?
- Se a condição de corrida estiver em atualizações para algumas estruturas de dados compartilhadas na memória, você precisará sincronizar o acesso e as atualizações da estrutura de dados de maneira apropriada.
- Se a condição de corrida estiver em atualizações no seu banco de dados, você precisará reestruturar seu SQL para usar transações no nível apropriado de granularidade.
- Não é uma má ideia fazer testes de carga antes de entrar em produção. Mais carga pode causar condição de corrida rara. Melhor corrigi-lo antes de corrigi-lo mais tarde.
- Certifique-se de não ter nenhuma variável global na qual você escreve.
- Em Java, cada objeto tem um e apenas um monitor e mutex associado a ele. O monitor único tem várias portas, no entanto, cada uma indicada pela palavra-chave
synchronized
. Quando um thread passa pela palavra-chavesynchronized
, ele efetivamente bloqueia todas as portas. - É claro que, se um thread não passar pela palavra-chave
synchronized
, ele não trancou a porta e algum outro thread pode entrar livremente a qualquer momento.