Session 12 |
Race condition
A shared resource may become corrupted if it is accessed simultaneously by multiple threads. The following example demonstrates the problem.
Suppose we create and launch several threads, each of which increments the counter by one. Here we will not examine in detail which steps the virtual machine makes to complete the task — it is enough to know that a single expression n = n + 1
can be decomposed into three steps:
- Retrieve the current value of
n
. - Increment the retrieved value by 1.
- Store the incremented value in
n
.
Suppose thread A invokes the increment task at about the same time as thread B. If two threads read the n
-value at the same time, they both get n + 1
as the result of the increment and both save the n + 1
as a new value of n
. If the timing of the threads is a bit offset, then the first thread increases the value of n
and then the second thread increases the value; the final result of the manipulations is n + 2
. A correct program should always save n + 2
to the memory as the result of two increments.
In the following example, a problem may arise where two threads manipulate with the same Counter
-type object at the same time. If the condition of if
-statement in the Counter
class is true, something like this is displayed: initial = 2860, n = 3700. (If this does not appear, run the program one more time because the problem may not happen every time.)
public class Counter { private int n = 0; public void increment() { int initial = n; n = n + 1; if (n > initial + 1) { System.out.println("initial = " + initial + ", n = " + n); } } } public class RaceCondition implements Runnable { private Counter counter; private String name; public RaceCondition(Counter counter, String name) { this.counter = counter; this.name = name; } public void run() { System.out.println(name + " started"); for (int i = 0; i < 10000000; i++) { counter.increment(); } System.out.println(name + " stopped"); } public static void main(String[] args) throws Exception { Counter counter = new Counter(); Thread t1 = new Thread(new RaceCondition(counter, "Thread-1")); Thread t2 = new Thread(new RaceCondition(counter, "Thread-2")); // Start threads t1.start(); t2.start(); // Waits for threads to finish t1.join(); t2.join(); System.out.println("Ready!"); } }
The above program demonstrates a race condition - a situation when two or more threads can access shared data and they try to change it at the same time. Because the thread scheduling algorithm can swap between threads at any time, you don't know the order in which the threads will attempt to access the shared data. Therefore, the result of the change in data is dependent on the thread scheduling algorithm, i.e. both threads are "racing" to access/change the data.
A class is said to be thread-safe if an object of the class does not cause a race condition in the presence of multiple threads. As demonstrated in the preceding example, the Counter
class is not thread-safe. How to avoid race condition, see the next page :)
Session 12 |