Why do we need a new keyword to solve a race condition? Could we reengineer our algorithms so that race conditions do not exist? Let’s see if we can reengineer the AsyncReadSocket class not to have a race condition by using trial and error (obviously not the best programming technique, but one that is useful for our purposes). We’ll conclude that it is impossible to solve a race condition without direct support from the virtual machine, because everything that we might try requires two operations: testing and setting variable. Without some process in the virtual machine to ensure that nothing happens to the variable after it is tested and before it is set, a race condition can occur. But the investigation into a possible way to avoid the race condition will provide us with an important tool for our later use—the BusyFlag class.
At first glance, the easiest way to make sure that the two threads do
not try to change the result
variable, or any
buffer at the same time, is to use the concept of a busy flag: if a
thread needs to access the result
variable, it
must set the flag to busy. If the flag is already busy, the thread
must wait until the flag is free, at which point it must set the flag
to busy. The thread is then free to access the buffer without fear of
it being accessed by the other thread. Once the task is completed,
the thread must set the flag to not busy.
Here’s a possible implementation of the busy flag:
public class BusyFlag { protected Thread busyflag = null; public void getBusyFlag () { while (busyflag != Thread.currentThread()) { if (busyflag == null) busyflag = Thread.currentThread(); try { Thread.sleep(100); } catch (Exception e) {} } } public void freeBusyFlag () { if (busyflag == Thread.currentThread()) { busyflag = null; } } }
This BusyFlag class contains two methods. The method
getBusyFlag()
sits in a loop until it is
able to set the busyflag
to the current thread.
As long as the busyflag
is set to another
thread, our thread waits for 100 milliseconds. As soon as the flag is
set to null
, our thread sets it to the current
thread. The other method,
freeBusyFlag()
, frees the flag by setting
it back to null
. This implementation seems to
solve the problem simply and elegantly. But it does not.
Why do we need to sleep for 100 milliseconds?
Because there seems to be no way to detect changes in the flag
without a polling loop. However, a polling loop that does not
sleep()
simply wastes CPU cycles that can be
used by other threads. At the other extreme, it takes a minimum of
100 milliseconds to set the busy flag even if no thread is holding
the flag in the first place. A possible enhancement that addresses
this problem may be as simple as making the sleep time a variable,
but no matter what we configure the time to be, we will be balancing
whether we want to be able to set the flag in a decent amount of time
versus the CPU cycles wasted in a polling loop.
Why do we sleep for 100 milliseconds even if the flag is
not set? This is actually intentional. There is a race
condition between the check to see if the flag is
null
and setting the flag. If two threads find
that the flag is free, they can each set the flag and exit the loop.
By calling the sleep()
method, we allow the two
threads to set busyflag
before checking it again
in the while
loop. This way, only the second
thread that sets the flag can exit the loop, and hence exit the
getBusyFlag()
method.
Of course, this is still a problem. As unlikely as it seems, it is still possible that this order of execution might occur:
Thread A detects that the busyflag
is free.
Thread B detects that the busyflag
is free.
Thread B sets the busyflag
.
Thread B sleeps for 100 milliseconds.
Thread B wakes up, confirms that it has the
busyflag
, and exits the loop.
Thread A sets the busyflag
, sleeps, wakes up,
confirms it has the busyflag
, and exits the
loop.
This is an extremely unlikely occurrence, but possible nonetheless; hence, this code is not one that most programmers are willing to accept.
We could use the BusyFlag class to replace the synchronized method in our Account class like this:
public class Account { private float total; private flag = new BusyFlag(); public boolean deduct(float t) { boolean succeed = false; flag.getBusyFlag(); if (t <= total) { total -= t; succeed = true; } flag.freeBusyFlag(); return succeed; } }
The vast majority of the time, this BusyFlag class works. However, even if you ran a huge beta test across 100 bank ATMs for a period of one year without a single problem, would you be willing to bet your career on a AutomatedTeller class that uses our BusyFlag class?
What if multiple threads set the busyflag at the same
moment? Is the act of setting the busyflag variable
atomic? The Java specification guarantees that setting any
variable other than a double or a long is atomic, so in this example,
it does not matter if multiple threads attempt to set the flag at the
same moment. In the case where two threads are setting a long or a
double, however, it is possible that the variable will be set
incorrectly: part of the variable will contain the bits set by the
first thread and the rest of the variable will contain the bits set
by the second thread. However, atomicity does not insure thread
communication; see the discussion of volatile
in
Appendix A.
Can we fix our BusyFlag class with the synchronization primitives? The problems that we encountered in the BusyFlag class are the same problems the BusyFlag class was meant to solve in the first place. This means that we can fix the problems in the BusyFlag class by using the synchronization primitives; we could use the BusyFlag class to solve other race conditions without worrying that it might break under certain conditions. The implementation (still not optimal) that solves this problem follows:
public class BusyFlag { protected Thread busyflag = null; public void getBusyFlag() { while (tryGetBusyFlag() == false) { try { Thread.sleep(100); } catch (Exception e) {} } } public synchronized boolean tryGetBusyFlag() { if (busyflag == null) { busyflag = Thread.currentThread(); return true; } return false; } public synchronized void freeBusyFlag() { if (busyflag == Thread.currentThread()) { busyflag = null; } } }
In this implementation of the BusyFlag class, we introduced a new
method called tryGetBusyFlag()
. It is
essentially the same as the getBusyFlag()
method
except that it does not wait until the flag is free. If the flag is
free, it sets the flag and returns true
. Otherwise
it returns false
. You’ll notice that this
method is declared as synchronized. This means the system makes sure
the thread that makes the call to the
tryGetBusyFlag()
method has grabbed the object
lock during the execution of the method.
The freeBusyFlag()
method is also declared as
synchronized: the thread that made the method call must also grab the
object lock before it can continue. Since there is only one object
lock for each instance of the class, the lock that
freeBusyFlag(
) will try to grab is the same lock
tryGetBusyFlag()
will grab. This means that
there will be no race condition between threads trying to get the
busyflag
and the thread that frees the
busyflag
.