Haben Sie eine Race-Bedingung im Java-Beispiel für Multi-Threading-Parallelität bemerkt? Wie man damit umgeht?
Veröffentlicht: 2019-12-11 Vor einiger Zeit habe ich einen Artikel über Producer Consumer Example geschrieben und wie man Lese-/Schreibvorgänge in Java besser handhabt. In ähnlicher Weise werden wir in diesem Tutorial etwas über Race Condition
und Thread locking
besprechen.
Wenn Sie eine der folgenden Fragen haben, dann sind Sie hier genau richtig:
- Beispiel für eine Java-Rennbedingung
- Mutex-Java-Beispiel
- Multithreading – Was ist eine Race Condition?
- Rennbedingungen und kritische Abschnitte
- Was ist Race-Condition?
- Umgang mit Race Condition in Java mit Beispiel
Warum tritt Race Condition in Java auf?
Eine Racebedingung in Java tritt auf, wenn two or more threads
same time
versuchen, gemeinsam genutzte Daten zu ändern/aktualisieren.
Werfen wir einen Blick auf die folgende Programmlogik:
Dies ist ein sehr einfaches Bankbeispiel, bei dem Sie
100 times
Beträgedeposit
undwithdraw
. Sie werden insgesamt 100 Mal 100 $ einzahlen = 100 $ x 100 = 10.000 $ und Sie werden 100 Mal insgesamt 50 $ abheben = 50 $ x 100 = 5.000 $. Am Ende des Programmabschlusses sollten Sie 5000 $ auf Ihrer Bank haben.
Hier sind die Schritte:
- Erstellen Sie die Klasse CrunchifyRaceCondition.java
- Erstellen Sie die Klasse CrunchifyTransaction.java
- Erstellen Sie die Klasse CrunchifyBankAccount.java
- Wir werden die Klasse CrunchifyRaceCondition ausführen und sie wird die Ein- und Auszahlungsschleife 100 Mal starten.
- Wir werden es
with Synchronized block
ausführen, um das Ergebnis zu überprüfen - Wir werden es
without Synchronized block
ausführen, um das Ergebnis zu überprüfen
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 ; } } } |
Bitte überprüfen Sie die Zeilen 24 und 34 oben. Behalten Sie das Schlüsselwort Synchronized
bei und führen Sie Ihr Programm aus. Sie sollten das korrekte Ergebnis sehen, wie Sie es im folgenden Bild sehen.
Entfernen Sie nun das Schlüsselwort „synced“ aus den Zeilen 24 und 34 und führen Sie dasselbe Programm aus.
Möglicherweise müssen Sie dieses Programm mehrmals ausführen, um ein Problem zu sehen. In Java gibt es keine Garantie, dass Sie Race Condition immer sehen.
Wenn Sie eine Anwendung auf Unternehmensebene haben und von Millionen von Transaktionen pro Sekunde sprechen, kann die Race Condition eine Katastrophe für Ihr Unternehmen verursachen.
Nun stellt sich die Frage, wie Sie Race Condition in Ihrer Java-Anwendung vermeiden können.
- Wenn die Racebedingung in Aktualisierungen einiger gemeinsam genutzter In-Memory-Datenstrukturen auftritt, müssen Sie den Zugriff und die Aktualisierungen auf die Datenstruktur auf geeignete Weise synchronisieren.
- Wenn die Racebedingung in Aktualisierungen Ihrer Datenbank auftritt, müssen Sie Ihre SQL neu strukturieren, um Transaktionen mit der entsprechenden Granularitätsebene zu verwenden.
- Es ist keine schlechte Idee, Lasttests durchzuführen, bevor Sie in die Produktion gehen. Mehr Last kann seltene Race Condition verursachen. Reparieren Sie es besser, bevor Sie es später reparieren.
- Stellen Sie sicher, dass Sie keine globale Variable haben, in die Sie schreiben.
- In Java ist jedem Objekt ein und nur ein Monitor und Mutex zugeordnet. Der einzelne Monitor enthält jedoch mehrere Türen, die jeweils durch das Schlüsselwort
synchronized
“ gekennzeichnet sind. Wenn ein Thread dassynchronized
Schlüsselwort passiert, sperrt er effektiv alle Türen. - Wenn ein Thread das
synchronized
Schlüsselwort nicht passiert, hat er die Tür natürlich nicht verschlossen, und ein anderer Thread kann jederzeit frei hereinplatzen.