Multithreading — Thread interference, Race Condition and Synchronization

Prateek
2 min readOct 21, 2021

In a multithreaded application, when multiple threads access and modify the ‘same data’, the behavior cannot always be predictable.

public class ThreadInterferenceDemo {
private Integer counter = 0;

class Task implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
performTask();
}
}

private void performTask() {
int temp = counter;
counter++;
System.out.println(Thread.currentThread().getName() + " - before: " + temp + " after: " + counter);
}
}

public static void main(String[] args) {
ThreadInterferenceDemo demo = new ThreadInterferenceDemo();
Thread thread1 = new Thread(demo.new Task());
Thread thread2 = new Thread(demo.new Task());
thread1.start();
thread2.start();
}
}

Output:

On running above code multiple times, we occasionally see unexpected results. For example:

Thread-0 - before: 0 after:2
Thread-0 - before: 2 after:3
Thread-1 - before: 1 after:2
Thread-0 - before: 3 after:4
Thread-0 - before: 5 after:6
Thread-0 - before: 6 after:7
Thread-1 - before: 4 after:5
Thread-1 - before: 7 after:8
Thread-1 - before: 8 after:9
Thread-1 - before: 9 after:10

There are two problems: first line of output is incrementing the counter from 0 to 2. Second problem is, across multiple call of performTask() method, the counter increment should be gradual.

Both problems are caused by the fact that the two threads are occasionally interleaving each other. The interleaving threads are said to be interfering each other. The interfering threads’ behavior is also termed as race condition.

Race conditions happen when the processes or threads depend on some shared state. Operations upon shared states are critical sections that must be mutually exclusive. Failure to obey this rule opens up the possibility of corrupting the shared state.

To avoid thread interference, Java provides a very easy solution for us, i.e. using the keyword synchronized in the method signature. Using this keyword requires each thread to get the 'Intrinsic Locks' before executing the method. This basically forces multiple threads to access the method one by one instead of executing it at the same time:

public class ThreadSynchronizedDemo {
private Integer counter = 0;

public static void main(String[] args) throws InterruptedException {
ThreadSynchronizedDemo demo = new ThreadSynchronizedDemo();
Thread thread1 = new Thread(demo.new Task());
Thread thread2 = new Thread(demo.new Task());
thread1.start();
thread2.start();
}

private synchronized void performTask() {
int temp = counter;
counter++;
System.out.println(Thread.currentThread().getName() + " - before: " + temp + " after:" + counter);
}

private class Task implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
performTask();
}
}
}
}

Output
Thread-0 — before: 0 after:1
Thread-0 — before: 1 after:2
Thread-0 — before: 2 after:3
Thread-0 — before: 3 after:4
Thread-0 — before: 4 after:5
Thread-1 — before: 5 after:6
Thread-1 — before: 6 after:7
Thread-1 — before: 7 after:8
Thread-1 — before: 8 after:9
Thread-1 — before: 9 after:10

--

--