10.2 Race Conditions
A race condition occurs when two
threads attempt to use the same resource at the same time. The
following class demonstrates a simple race condition. Two threads
simultaneously try to increment a counter. If each thread can
complete the increment( ) method in its entirety
without the other thread executing, then all is fine, and the counter
monotonically increases. Otherwise, the thread context switcher has
the opportunity to interrupt one thread in the middle of executing
the increment( ) method and let the other thread
run through this method. Note that the thread can actually be
interrupted anywhere, not necessarily in the middle of the
increment( ) method, but I've
greatly increased the likelihood of an interruption in the
increment( ) method by including a
print statement there:
package tuning.threads;
public class ThreadRace
implements Runnable
{
//global counter
static int num=0;
public static void increment( )
{
int n = num;
//This next line gives the context switcher an ideal
//place to switch context.
System.out.print(num+" ");
//And when it switches back, n will still be the old
//value from the old thread.
num = n + 1;
}
public static void main(String args[ ])
{
ThreadRace d1 = new ThreadRace( );
ThreadRace d2 = new ThreadRace( );
Thread d1Thread = new Thread(d1);
Thread d2Thread = new Thread(d2);
d1Thread.start( );
d2Thread.start( );
}
public void run( )
{
for (int i = 200; i >= 0 ; i--)
{
increment( );
}
}
}
The output from executing this class on a single-processor test
machine is:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
31 32 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 33 34 35 36 37 38 39 40 41
42 43 44 45 46 47 48 49 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
You see that after 16, the next number is 16 again, and after the
first 32, the next number is 17, as the threads switch back and forth
in the middle of the increment( ) method. On a
multiprocessor machine, the situation is even more confused.
Synchronizing the increment(
) method ensures the correct behavior of a monotonically
increasing counter, as this gives exactly the desired behavior: the
method is forced to complete before another call to it from any
thread can be started.
|
In this test, because the
counter is static, the increment(
) method needs to be static for
synchronization to work correctly. If the increment(
) method is not static, synchronizing it
locks the monitor for each this object rather than
for the class. In the example I used a different object in each
thread. A non-static increment(
) method is synchronized separately on each
this object, so the updates remain unsynchronized
across the two threads.
It is not simply that the num variable is
static (though it needs to be for this particular
example to work). The critical point is that the monitor that locks
the method must be the same monitor for the two threads; otherwise,
each thread gains its own separate
lock
with no synchronization occurring.
Generally, deciding what to synchronize can be quite subtle, and you
need to keep in mind which monitor is going to be locked by any
particular thread.
|
|
|