Ați observat Condiția de cursă în exemplul de concurență multi-threading Java? Cum să te descurci cu asta?
Publicat: 2019-12-11 Cu ceva timp în urmă, am scris un articol despre Producer Consumer Example și despre cum să gestionez mai bine operațiunea de citire/scriere în Java. Pe o notă similară, în acest tutorial vom discuta ceva despre Race Condition
și Thread locking
.
Dacă aveți oricare dintre întrebările de mai jos, vă aflați la locul potrivit:
- Exemplu de condiție de cursă Java
- mutex java exemplu
- multithreading – Ce este o condiție de cursă?
- Condiții de cursă și secțiuni critice
- Ce este condiția de cursă?
- Cum să faci față condiției de cursă în Java cu Exemplu
De ce apare condiția de cursă în Java?
Condiția de cursă în Java apare atunci când two or more threads
de execuție încearcă să modifice/actualizeze datele partajate în same time
.
Să aruncăm o privire la logica programului de mai jos:
Acesta este un exemplu bancar foarte simplu în care veți
deposit
șiwithdraw
sume de100 times
. Veți depune 100 USD în total de 100 de ori = 100 USD x 100 = 10.000 USD și veți retrage 50 USD în total de 100 de ori = 50 USD x 100 = 5.000 USD. La sfârșitul programului, ar trebui să aveți 5000 USD în bancă.
Iată pașii:
- Creați clasa CrunchifyRaceCondition.java
- Creați clasa CrunchifyTransaction.java
- Creați clasa CrunchifyBankAccount.java
- Vom rula clasa CrunchifyRaceCondition și va începe depunerea și retragerea buclei de 100 de ori.
- Îl vom rula
with Synchronized block
pentru a verifica rezultatul - Îl vom rula
without Synchronized block
pentru a verifica rezultatul
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 ; } } } |
Vă rugăm să verificați rândurile 24 și 34 de mai sus. Păstrați acel cuvânt cheie Synchronized
și rulați programul. Ar trebui să vedeți rezultatul corect, așa cum îl vedeți în imaginea de mai jos.
Acum eliminați cuvântul cheie sincronizat din rândurile 24 și 34 și rulați același program.
Poate fi necesar să rulați acest program de mai multe ori pentru a vedea o problemă. În Java nu există nicio garanție că veți vedea starea cursei tot timpul.
Dacă aveți o aplicație la nivel de întreprindere și vorbiți de milioane de tranzacții pe secundă, atunci condiția de cursă poate provoca un dezastru pentru compania dumneavoastră.
Acum întrebarea este cum să eviți Race Condition în aplicația ta Java?
- Dacă condiția de cursă este în actualizări ale unor structuri de date partajate în memorie, trebuie să sincronizați accesul și actualizările la structura de date în mod corespunzător.
- Dacă condiția de cursă se află în actualizări ale bazei de date, trebuie să vă restructurați SQL pentru a utiliza tranzacțiile la nivelul adecvat de granularitate.
- Nu este o idee rea să faceți teste de încărcare înainte de a intra live în producție. Mai multă sarcină poate cauza Condiții rare de cursă. Mai bine să-l reparăm înainte, mai degrabă să-l repari mai târziu.
- Asigurați-vă că nu aveți nicio variabilă globală în care scrieți.
- În Java, fiecare obiect are un singur monitor și un mutex asociat cu el. Unicul monitor are mai multe uși în el, totuși, fiecare fiind indicată de cuvântul cheie
synchronized
. Când un fir trece peste cuvântul cheiesynchronized
, blochează efectiv toate ușile. - Desigur, dacă un fir nu trece peste cuvântul cheie
synchronized
, acesta nu a blocat ușa și un alt fir este liber în orice moment.