Race Condition in Java
Java is a multi-threaded programming language, race conditions are more likely to arise. Mostly because data can change when multiple threads visit the same resource simultaneously. Race conditions are concurrency bugs, as we might say. Deadlock in Java is strongly connected to this. We shall implement this race condition using Java in this section.
What is a race condition?
A scenario in which two or more threads are simultaneously running the critical section is a piece of the programme that accesses shared memory. It causes a program to misbehave.
To put it simply, a race condition is a situation wherein more than two threads contend with one another for access to shared resources.
When thread B tries to delete data that process A is accessing from the linked list, for instance, a race condition is created by this process, which could lead to run-time errors.
Two different racial conditions exist that are:
- Read-modify-write
- Check-then-act
The read-modify-write pattern denotes that more than one thread will read a variable, modify the value, and then write the changed value back to the original variable. Take a look at the below line of code.
public class number
{
protected long num = 10;
public void addition (long ele)
{
this.num = this.num + ele;
}
}
Many threads interact with a single object without necessary synchronization, and their actions overlap.
Race Condition Example
Let's say that two processes, A and B, are running on separate CPUs. Concurrently, both processes are attempting to call the bankAccount() function. The shared variable we will use in the function has the value 1000.
Imagine that A calls the method bankAccount() and passes it the number 200. Like process A, process B also calls the function bankAccount() while passing a parameter of 100.
The outcome appears as follows:
- 1100 is loaded into the CPU register by Process A.
- 1100 will be put into Process B's registry.
- Process A will increase its register by 200, producing the number 1300.
- Process B will multiply its register by 100, yielding a calculated result of 1200.
- A and B processes will store 1400 and 1150, respectively, in a global variable.
Race1.java
class Counter implements Runnable
{
private int count = 0;
public void increment ()
{
try
{
Thread.sleep(100);
}
catch (InterruptedException ae)
{
ae.printStackTrace();
}
cou++;
}
public void decrement ()
{
cou--;
}
public int getValue()
{
return you;
}
@Override
public void run()
{
// Increasing the thread value
this.increment();
System. out.println("After increment the Value for Thread " + Thread.currentThread().getName() + " " + this.getValue());
// Decreasing the thread value
this.decrement();
System.out.println("Last thread has a value : " + Thread.currentThread().getName() + " " + this.getValue());
}
}
public class Race1
{
public static void main(String args[])
{
Counter C = new Counter();
Thread thread1 = new Thread(C, "Thread1");
Thread thread2 = new Thread(C, "Thread2");
Thread thread3 = new Thread(C, "Thread3");
thread1.start();
thread2.start();
thread3.start();
}
}
Output
After increment the Value for Thread Thread3 2
Last thread has a value : Thread3 1
After increment the Value for Thread Thread1 2
Last thread has a value : Thread1 0
After increment the Value for Thread Thread2 2
Last thread has a value : Thread2 -1
How to avoid Race condition
The following two options can help you prevent racial situations.
- Mutual exclusion
- Synchronize the process
One should ensure that just one operation can access the data stream simultaneously to avoid race situations. The fundamental justification for synchronizing the processes is this.
Mutual exclusion is another way to prevent racial conditions. When using a shared variable or thread, a thread would exclude itself from performing the same thing in mutual exclusion.
Race2.java
class Counter implements Runnable
{
private int count = 0;
public void increment()
{
try
{
Thread.sleep(100);
}
catch (InterruptedException ae)
{
ae.printStackTrace();
}
cou++;
}
public void decrement()
{
cou--;
}
public int getValue()
{
return you;
}
@Override
public void run()
{
synchronized(this)
{
// Increasing the thread value
this.increment();
System. out.println("After increment the Value for Thread " + Thread.currentThread().getName() + " " + this.getValue());
// Decreasing the thread value
this.decrement();
System.out.println("Last thread has a value : " + Thread.currentThread().getName() + " " + this.getValue());
}
}
}
public class Race2.java
{
public static void main(String args[])
{
Counter counter = new Counter();
Thread thread1 = new Thread(counter, "Thread1");
Thread hread2 = new Thread(counter, "Thread2");
Thread hread3 = new Thread(counter, "Thread3");
thread1.start();
hread2.start();
hread3.start();
}
}
Output
After increment the Value for Thread Thread3 1
Last thread has a value : Thread3 0
After increment the Value for Thread Thread1 1
Last thread has a value : Thread1 0
After increment the Value for Thread Thread2 1
Last thread has a value : Thread2 0