have-you-noticed-race-condition-in-java-multi-threading-concurrency-example

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 and withdraw amounts 100 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:

  1. Create class CrunchifyRaceCondition.java
  2. Create class CrunchifyTransaction.java
  3. Create class CrunchifyBankAccount.java
  4. We will run class CrunchifyRaceCondition and it will start deposit and withdraw loop 100 times.
  5. We will run it with Synchronized block to check result
  6. 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.

Java Synchronized Block Keyword - Crunchify

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.

Java without Synchronized Block Keyword - Crunchify

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?

  1. 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.
  2. 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.
  3. 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.
  4. Make sure you have no global variable that you write to.
  5. 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 the synchronized keyword, it effectively locks all the doors.
  6. 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.

The post Have you noticed Race Condition in Java Multi-threading Concurrency Example? How to deal with it? appeared first on Crunchify.