Avez-vous remarqué Race Condition in Java Multi-threading Concurrency Example? Comment y faire face?
Publié: 2019-12-11 Il y a quelque temps, j'ai écrit un article sur Producer Consumer Example et comment mieux gérer les opérations de lecture/écriture en Java. Dans le même ordre d'idées, dans ce didacticiel, nous aborderons quelque chose sur Race Condition
et Thread locking
.
Si vous avez l'une des questions ci-dessous, vous êtes au bon endroit :
- Exemple de condition de concurrence Java
- exemple mutex java
- multithreading – Qu'est-ce qu'une condition de concurrence ?
- Conditions de course et sections critiques
- Qu'est-ce que la condition de course ?
- Comment gérer la condition de concurrence en Java avec l'exemple
Pourquoi la condition de concurrence en Java se produit-elle ?
La condition de concurrence en Java se produit lorsque two or more threads
tentent de modifier/mettre à jour des données partagées en same time
.
Jetons un coup d'œil à la logique du programme ci-dessous :
Il s'agit d'un exemple bancaire très simple dans lequel vous
deposit
etwithdraw
montants100 times
. Vous déposerez 100 $ au total 100 fois = 100 $ x 100 = 10 000 $ et vous retirerez 50 $ au total 100 fois = 50 $ x 100 = 5 000 $. À la fin du programme, vous devriez avoir 5 000 $ dans votre banque.
Voici les étapes :
- Créer la classe CrunchifyRaceCondition.java
- Créer la classe CrunchifyTransaction.java
- Créer la classe CrunchifyBankAccount.java
- Nous exécuterons la classe CrunchifyRaceCondition et elle commencera la boucle de dépôt et de retrait 100 fois.
- Nous allons l'exécuter
with Synchronized block
pour vérifier le résultat - Nous allons l'exécuter
without Synchronized block
pour vérifier le résultat
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 ; } } } |
Veuillez vérifier les lignes 24 et 34 ci-dessus. Conservez ce mot-clé Synchronized
et exécutez votre programme. Vous devriez voir le résultat correct comme vous le voyez dans l'image ci-dessous.
Supprimez maintenant le mot clé synchronized des lignes 24 et 34 et exécutez le même programme.
Vous devrez peut-être exécuter ce programme plusieurs fois pour voir un problème. En java, il n'y a aucune garantie que vous verrez la condition de course tout le temps.
Si vous avez une application au niveau de l'entreprise et que vous parlez de millions de transactions par seconde, la condition de concurrence peut entraîner un désastre pour votre entreprise.
Maintenant, la question est de savoir comment éviter la condition de concurrence dans votre application Java ?
- Si la condition de concurrence est dans les mises à jour de certaines structures de données en mémoire partagées, vous devez synchroniser l'accès et les mises à jour de la structure de données de manière appropriée.
- Si la condition de concurrence est dans les mises à jour de votre base de données, vous devez restructurer votre SQL pour utiliser les transactions au niveau de granularité approprié.
- Ce n'est pas une mauvaise idée de faire des tests de charge avant de passer en production. Plus de charge peut provoquer une condition de course rare. Mieux vaut le réparer avant plutôt le réparer plus tard.
- Assurez-vous que vous n'avez pas de variable globale dans laquelle vous écrivez.
- En Java, chaque objet a un et un seul moniteur et mutex qui lui sont associés. Le moniteur unique a plusieurs portes, cependant, chacune indiquée par le mot-clé
synchronized
. Lorsqu'un thread passe sur le mot clésynchronized
, il verrouille effectivement toutes les portes. - Bien sûr, si un thread ne passe pas par le mot clé
synchronized
, il n'a pas verrouillé la porte, et un autre thread est libre d'intervenir à tout moment.