Hai notato Race Condition nell'esempio di concorrenza multi-threading Java? Come affrontarlo?
Pubblicato: 2019-12-11 Qualche tempo fa ho scritto un articolo su Producer Consumer Example e su come gestire meglio le operazioni di lettura/scrittura in Java. Sulla nota simile, in questo tutorial discuteremo qualcosa su Race Condition
e Thread locking
.
Se hai una delle seguenti domande, sei nel posto giusto:
- Esempio di condizioni di gara Java
- esempio java mutex
- multithreading: cos'è una race condition?
- Condizioni di gara e sezioni critiche
- Che cos'è la condizione di razza?
- Come gestire la condizione di gara in Java con l'esempio
Perché si verifica una condizione di razza in Java?
La race condition in Java si verifica quando two or more threads
tentano di modificare/aggiornare i dati condivisi same time
.
Diamo un'occhiata alla logica del programma di seguito:
Questo è un esempio bancario molto semplice in cui
deposit
ewithdraw
importi100 times
. Depositerai $ 100 in totale 100 volte = $ 100 x 100 = $ 10.000 e preleverai $ 50 in totale 100 volte = $ 50 x 100 = $ 5.000. Alla fine del completamento del programma dovresti avere $ 5000 nella tua banca.
Ecco i passaggi:
- Crea classe CrunchifyRaceCondition.java
- Crea classe CrunchifyTransaction.java
- Crea classe CrunchifyBankAccount.java
- Eseguiremo la classe CrunchifyRaceCondition e inizierà il ciclo di depositi e prelievi 100 volte.
- Lo eseguiremo
with Synchronized block
per verificare il risultato - Lo eseguiremo
without Synchronized block
per verificare il risultato
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 ; } } } |
Si prega di controllare le righe 24 e 34 sopra. Mantieni quella parola chiave Synchronized
ed esegui il tuo programma. Dovresti vedere il risultato corretto come lo vedi nell'immagine sottostante.
Ora rimuovi la parola chiave sincronizzata dalle righe 24 e 34 ed esegui lo stesso programma.
Potrebbe essere necessario eseguire questo programma più volte per visualizzare un problema. In Java non vi è alcuna garanzia che vedrai sempre le condizioni di gara.
Se si dispone di un'applicazione di livello aziendale e si parla di milioni di transazioni al secondo, le condizioni di gara potrebbero causare disastri per la tua azienda.
Ora la domanda è come evitare Race Condition nella tua applicazione Java?
- Se la race condition è negli aggiornamenti di alcune strutture dati in memoria condivise, è necessario sincronizzare l'accesso e gli aggiornamenti alla struttura dati in modo appropriato.
- Se la race condition è negli aggiornamenti del database, è necessario ristrutturare l'SQL per utilizzare le transazioni al livello di granularità appropriato.
- Non è una cattiva idea eseguire il test del carico prima di andare in produzione. Un carico maggiore può causare condizioni di gara rare. Meglio aggiustarlo prima piuttosto che aggiustarlo in seguito.
- Assicurati di non avere alcuna variabile globale su cui scrivere.
- In Java, ogni oggetto ha uno e un solo monitor e mutex ad esso associati. Il singolo monitor ha diverse porte, tuttavia, ciascuna indicata dalla parola chiave
synchronized
. Quando un thread passa sopra la parola chiavesynchronized
, blocca efficacemente tutte le porte. - Ovviamente, se un thread non passa attraverso la parola chiave
synchronized
, non ha chiuso a chiave la porta e qualche altro thread è libero di entrare in qualsiasi momento.