Notice that the
original getBusyFlag()
method is not declared as
synchronized. This is because it’s not necessary:
getBusyFlag()
does not try to access the
busyflag
variable. Instead, it calls the
tryGetBusyFlag()
method, which accesses the
busyflag
and is, of course, synchronized.
Let’s take another look at the
getBusyFlag()
method, one that does not call the
tryGetBusyFlag()
method. Instead, this version
gets the busyflag
directly:
public synchronized void getBusyFlag() { while (true) { if (busyflag == null) { busyflag = Thread.currentThread(); break; } try { Thread.sleep(100); } catch (Exception e) {} } }
Let’s assume that we do not want the inefficiency of an extra
method call to the tryGetBusyFlag()
method. In
our new version of the getBusyFlag()
method, we
now access the busyflag
directly. The
getBusyFlag()
method simply loops waiting for
the flag to be freed, sets the flag, and returns. Since we are now
accessing the busyflag
directly, we must make
the method synchronized or we will have a race condition.
Unfortunately, there is a problem when we declare this method to be
synchronized. While declaring the method synchronized prevents other
getBusyFlag()
and
tryGetBusyFlag()
methods from being run at the
same time (which prevents a race condition), it also prevents the
freeBusyFlag()
method from running. This means
that if the flag is busy when getBusyFlag()
is
called, getBusyFlag()
waits until the flag is
freed. Unfortunately, since the freeBusyFlag()
method will not run until the getBusyFlag()
method frees the object lock, the busyflag
will
not be freed. This Catch-22 situation is termed
deadlock. The deadlock in this case is a problem
between a lock and a busyflag
. More commonly,
deadlock occurs between two or more locks, but the idea is the same.
We have a problem in this implementation of
getBusyFlag()
because the scope in which we used
the object lock was too large. All we need to do is hold the lock for
the period during which we need to change the data (i.e., check and
get the busyflag
); it doesn’t need to be
held during the entire method. Fortunately, Java also provides us the
ability to synchronize a block of code instead of synchronizing the
entire method. Using this block synchronization mechanism on our
getBusyFlag()
method, we now obtain the
following code:
public void getBusyFlag () { while (true) { synchronized (this) { if (busyflag == null) { busyflag = Thread.currentThread(); break; } } try { Thread.sleep(100); } catch (Exception e) {} } }
In this new implementation of the getBusyFlag()
method, we only synchronized the period between checking the flag and
setting it if it is not busy. This usage is very similar to the
synchronized method usage, except that the scope during which the
lock is held is much smaller.
Interestingly, this usage not only gives us more precise control over
when the object lock is held, but it also allows us to select which
object’s lock to grab. In this case, since we want to grab the
same object lock as in the tryGetBusyFlag()
and
freeBusyFlag()
methods, we chose
this
as the object on which to obtain the lock.
For synchronized methods, the lock that is obtained is the object
lock of the class in which the method exists; in other words,
the
this
object.