Sometime back I’ve written an article on Producer Consumer Example and how to handle read/write operation better way in Java. On the similar note, in this tutorial we will discuss something on Race Condition
and Thread locking
.
If you have any of the below questions then you are at right place:
- Java race condition example
- mutex java example
- multithreading – What is a race condition?
- Race Conditions and Critical Sections
- What is race condition?
- How to deal with Race Condition in Java with Example
Why Race Condition in Java Occurs?
Race condition in Java occurs when two or more threads
try to modify/update shared data at the same time
.
Let’s take a look at below Program logic:
This is very simple banking example in which you will
deposit
andwithdraw
amounts100 times
. You will deposit $100 total 100 times = $100 x 100 = $10,000 and you will withdraw $50 total 100 times = $50 x 100 = $5,000. At the end of program completion you should have $5000 in your bank.
Here are the steps:
- Create class CrunchifyRaceCondition.java
- Create class CrunchifyTransaction.java
- Create class CrunchifyBankAccount.java
- We will run class CrunchifyRaceCondition and it will start deposit and withdraw loop 100 times.
- We will run it
with Synchronized block
to check result - We will run it
without Synchronized block
to check result
CrunchifyRaceCondition.java
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
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
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; } } }
Please check line 24 and 34 above. Keep that Synchronized
keyword and run your program. You should see correct result as you see it in below image.
Now remove synchronized keyword from line 24 and 34 and run the same program.
You may need to run this program multiple times to see an issue. In java there is no guarantee you will see Race condition all the times.
If you have enterprise level application and you are talking of millions of transaction per seconds then race condition may cause disaster for your company.
Now question is how to avoid Race Condition in your Java Application?
- If the race condition is in updates to some shared in-memory data structures, you need to synchronize access and updates to the data structure appropriate way.
- If the race condition is in updates to the your database, you need to restructure your SQL to use transactions at the appropriate level of granularity.
- It’s not a bad idea to do Load testing before going live in production. More load may cause rare Race Condition. Better to fix it before rather fixing it later.
- Make sure you have no global variable that you write to.
- In Java, every object has one and only one monitor and mutex associated with it. The single monitor has several doors into it, however, each indicated by the
synchronized
keyword. When a thread passes over thesynchronized
keyword, it effectively locks all the doors. - Of course, if a thread doesn’t pass across the
synchronized
keyword, it hasn’t locked the door, and some other thread is free barge in at any time.