Implementing Counting Semaphore in Java: Synchronization and Race Condition Challenges, Papers of Programming Languages

The implementation of a java monitor for a counting semaphore, highlighting the challenges of ensuring safety, no barging, single signals, and passed back exceptions. The document also covers various attempts to address these challenges and the importance of understanding the low-level synchronization tool that java monitors represent.

Typology: Papers

Pre 2010

Uploaded on 07/29/2009

koofers-user-js7
koofers-user-js7 🇺🇸

10 documents

1 / 5

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
“Alfonse, Wait Here For My Signal!”
Stephen J. Hartley
Computer Science and Information Systems
Richard Stockton College, Pomona, NJ 08240-0195
+l 609 652 4968 [email protected]
Abstract
At first glance, Java monitors appear easy to use. How-
ever, a deeper analysis reveals that they are surprisingly
tricky, suffer from subtle race conditions, and are actu-
ally a low-level synchronization tool in stark contrast
to the reputation Java has as a modern well-engineered
language. The programmer is responsible for building
safe and robust synchronization structures from Java
monitors.
1 Java Is Multithreaded
A new thread in a Java program is created [2] by
subclassing the Thread class and overriding the run
method; or by implementing the Runnable interface,
which means providing an implementation of the run
method.
Once started, a Java thread is in one of several states:
runnable,
in the runnable or ready set; mnning on the
CPU; dead, when its run method completes;
blocked,
waiting for an event to occur such as completion of
an IO, joining with another thread, returning from a
sleep (ms > method call, acquiring an object’s lock, or
a monitor signal from another thread;
suspended
until
resumed.
Java threads have a user-settable priority. The Java
virtual machine (JVM) scheduler usually ensures that
the highest priority thread is running on the CPU, pre-
empting the currently running thread when necessary,
but this is not guaranteed by
The Java Language Spec-
ification [4,
page 4151.
Time slicing (round robin scheduling) of threads by
the JVM is optional: Windows 95/NT does, Solaris does
not.
A Java thread is
interrupted
when its interrupt
method is called by another thread. This call sets a
flag in the interrupted thread that the latter can check
Permission to make digital or hard copies of all or part of this work for
personal or classroom use is granted without fee provided that
copies are not made or distributed tar profit or CommerCial edvan-
tage
end that copies bear this notice and the full citation on the first page.
70 copy otherwise, to republish, to post on Servers Or to
redistribute to lists, requires prior specific permisslon and/or a fee.
SIGCSE’BB 3/99 New Orleans, LA, USA
0 1999 ACM l-581 13-085~6/99/0003...$5.00
periodically, allowing one thread to tell another thread
to stop itself or return allocated resources if it is not in
the middle of some critical operation. A thread should
check its interrupt flag before or after such operations
and take appropriate action when interrupted.
2 Object Locks
Every Java object has a lock. A thread executing a syn-
chronized statement
must acquire the specified object’s
lock, blocking if the object is already locked, before ex-
ecuting the statement. It acts like a binary semaphore
with initial value one and solves the mutual exclusion
critical section problem:
Object obj = new Object (> ; // methods share
. . .
synchronized (obj) ( // in several methods
. . . // any code, e.g., critical section
3
The Java Language Specification
does not guarantee
that the thread waiting the longest to lock an object
is the next thread to obtain the lock when the object is
unlocked.
The construct
synchronized
type
. . . body of method
3
q
ethod(. . .> C
is an abbreviation for
type method(. . .I <
synchronized (this) (
. . . body of method
3
-
that is, the entire body of the instance method is a
synchronized statement on the object (referenced with
this) the method is in.
3 Java Monitors
In addition to a lock, every Java object has methods
wait, notify, and notifyAll. A Java
monitor
has the
following structure or pattern.
58
pf3
pf4
pf5

Partial preview of the text

Download Implementing Counting Semaphore in Java: Synchronization and Race Condition Challenges and more Papers Programming Languages in PDF only on Docsity!

“Alfonse, Wait Here For My Signal!”

Stephen J. Hartley

Computer Science and Information Systems

Richard Stockton College, Pomona, NJ 08240-

+l 609 652 4968 [email protected]

Abstract

At first glance, Java monitors appear easy to use. How-

ever, a deeper analysis reveals that they are surprisingly

tricky, suffer from subtle race conditions, and are actu-

ally a low-level synchronization tool in stark contrast

to the reputation Java has as a modern well-engineered

language. The programmer is responsible for building

safe and robust synchronization structures from Java

monitors.

1 Java Is Multithreaded

A new thread in a Java program is created [2] by

subclassing the Thread class and overriding the run

method; or by implementing the Runnable interface,

which means providing an implementation of the run

method.

Once started, a Java thread is in one of several states:

runnable, in the runnable or ready set; mnning on the

CPU; dead, when its run method completes; blocked,

waiting for an event to occur such as completion of

an IO, joining with another thread, returning from a

sleep (ms> method call, acquiring an object’s lock, or

a monitor signal from another thread; suspended until

resumed.

Java threads have a user-settable priority. The Java

virtual machine (JVM) scheduler usually ensures that

the highest priority thread is running on the CPU, pre-

empting the currently running thread when necessary,

but this is not guaranteed by The Java Language Spec-

ification [4, page 4151.

Time slicing (round robin scheduling) of threads by

the JVM is optional: Windows 95/NT does, Solaris does

not.

A Java thread is interrupted when its interrupt

method is called by another thread. This call sets a

flag in the interrupted thread that the latter can check

Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed tar profit or CommerCial edvan- tageend that copies bear this notice and the full citation on the first page. 70 copy otherwise, to republish, to post on Servers Or to redistribute to lists, requires prior specific permisslon and/or a fee. SIGCSE’BB 3/99 New Orleans, LA, USA 0 1999 ACM l-581 13-085~6/99/0003...$5.

periodically, allowing one thread to tell another thread

to stop itself or return allocated resources if it is not in

the middle of some critical operation. A thread should

check its interrupt flag before or after such operations

and take appropriate action when interrupted.

2 Object Locks

Every Java object has a lock. A thread executing a syn-

chronized statement must acquire the specified object’s

lock, blocking if the object is already locked, before ex-

ecuting the statement. It acts like a binary semaphore

with initial value one and solves the mutual exclusion

critical section problem:

Object obj = new Object (> ; // methods share

synchronized (obj) ( // in several methods

... // any code, e.g., critical section

The Java Language Specification does not guarantee

that the thread waiting the longest to lock an object

is the next thread to obtain the lock when the object is

unlocked.

The construct

synchronized type

... body of method 3

qethod(.. .> C

is an abbreviation for

type method(.. .I <

synchronized (this) (

... body of method -^3

that is, the entire body of the instance method is a

synchronized statement on the object (referenced with

this) the method is in.

3 Java Monitors

In addition to a lock, every Java object has methods

wait, notify, and notifyAll. A Java monitor has the

following structure or pattern.

class Monitor extends... { private... // data fields: monitor state Monitor (.. .) C...) // constructor synchronized type methodic...) {

... notifyAll0; // if state altered while (! condition) try ( wait0 ; ) catch (InterruptedException e) {) ... notifyAll(); // if state altered 3 synchronized type method2 (... ) throws InterruptedException ( ... notifyAll(); // if state altered while (! condition) wait0 ; ... notifyAll0; // if state altered 3 ... 3

Some important things to note are the following.

l The thread blocked the longest on a monitor syn- chronized method call is not guaranteed to be next thread to acquire the monitor lock when the monitor lock is released.

l The thread blocked the longest in a monitor wait method call is not guaranteed to be the one re- moved from the wait set when a not if y method call is made by some other thread in the monitor [4, page 4171.

l Since the signaling discipline is signal-and-continue [l], barging is possible [3]: a thread waiting for the monitor lock to execute a monitor synchronized method might get the lock before a signaled thread reacquires the lock, even if the not if y occurred earlier than the monitor method call. Thus, in most situations a thread should recheck its wait- ing condition when signaled

while (! condition)... wait 0 ;

... notifyAll ;,

rather than unconditionally continuing after reen- tering the monitor.

if (! condition)... wait0 ;

... notify0 ;

l Each monitor object has a single nameless anony- mous condition variable. We cannot signal one of

several threads waiting on a specific condition with not if y. It is safer to use not if yAl1 to wake up all waiting threads and let them recheck their waiting conditions. However, this might be very inefficient and greatly increase overhead.

In some situations, we can use not if y in- stead of not if yAl1 and if... wait 0 instead of while... wait 0. However, it is extremely tricky as we will see!

A not if yAl1 needs to be done by a thread be- fore a wait if any state variables that might affect other thread waiting conditions were altered by the thread after entering the monitor. This also applies when a thread leaves the monitor (returns from a monitor method call).

If a thread that is blocked inside a call to sleep, join, or wait is interrupted, then these meth- ods clear the thread’s interrupt flag and throw an InterruptedException instead of returning nor- mally. Note that no exception is thrown if a thread is interrupted while blocked waiting to acquire a monitor’s lock to execute a synchronized method. In contrast, InterruptedException is thrown by wait if a thread that has been notified is inter- rupted while blocked, waiting to reacquire the mon- itor lock.

Ignoring Interrupt edExcept ion with an empty catch block is acceptable in

while (! condition) try ( wait0 ; 1 catch (InterruptedException e) ( 3

as long as we are using notifyAl instead of no- t if y. But it is not acceptable in

if (!condition) try c waito; 3 catch (InterruptedException e) ( 3

because a thread interrupted out of its wait then reenters the monitor without being notified. How- ever, it is more desirable in either case to have the containing method throw the exception back to the method’s caller so the latter knows an interrupt oc- curred, as in method2 above.

4 Sequence of Examples Suppose we want to implement a counting, semaphore with a Java monitor. Semaphores have two operations. P: If the semaphore’s value is 0 then block, else decre- ment the value. V: If one or more threads are blocked inside P then unblock one, else increment the semaphore’s value.

V will increment the value to 0 whereas it should now be 1. The fix for this is to count the wait set separately, rather than letting the semaphore value go negative. Another, more insidious problem is present: a race condition between interrupt ( ) and not if y ( ). Sup- pose several threads are blocked inside wait 0 and then one of them is notified and then interrupted before it reacquires the monitor lock. The not if y 0 gets “lost” in that one of the other waiting threads should now proceed. So we need to catch the exception when a thread is interrupted out of wait 0 and regenerate the notifyo.

4.4 Fourth Attempt

public class CountingSemaphore ( private int value = 0; private int waitcount = 0;

public CountingSemaphore(int initial) ( if (initial > 0) value = initial; )

public synchronized void P() throws InterruptedException ( if (value == 0 I I waitcount > 0) ( waitCount++; try f wait0 ; 1 catch (InterruptedException e) ( notify0 ; // regenerate throw e; 1 finally ( waitCount--; 1 3 value-- ; 3

public synchronized void V() < value++ ; if (waitcount > 0) notify0; 3 3

We have fixed the problem of a notified thread being interrupted, but we have introduced a new problem. Suppose a thread blocked inside wait 0 is interrupted before being notified. The not if y 0 it does in its catch block will move some other waiting thread, if there is one, out of the wait set into the lock (re)acquire set. When that thread gets back into the semaphore moni- tor, its P operation will complete successfully, an error. The fix is to add a notification flag so a thread can dis- tinguish between the notify0 in V and the notify in the catch block.

4.5 Fifth Attempt

public class CountingSemaphore ( private int value = 0;

private int waitcount = 0; private boolean notified = false;

public CountingSemaphore(int initial) ( if (initial > 0) value = initial; )

public synchronized void PO throws InterruptedException { if (value == 0 I I waitcount > 0) < waitCount++; try ( do ( wait(); ) while (!notified); 1 catch (InterruptedException e) C notify0 ; throw e; ) finally ( waitCount--; 1 notified = false; 3 value--; 3

public synchronized void V() ( value++ ; if (waitcount > 0) ( notified = true; notify0 ;

Suppose multiple threads are blocked inside wait 0 in P and some other thread calls V. Because several ad- ditional V calls might barge into the monitor before a notified thread reacquires the monitor lock, we must make the notification flag an integer counter to avoid “lost” notifies. 4.6 Sixth Attempt public class CountingSemaphore ( private int value = 0; private int waitcount = 0; private int notifycount = 0;

public CountingSemaphore(int initial) { if (initial > 0) value = initial; 1

public synchronized void PO throws InterruptedException ( if (value == 0 I I waitcount > 0) C waitCount++ ; try ( do C wait () ; 1 while (notifycount == 0); ) catch (InterruptedException a) ( notify0 ; throw e;

1 finally ( waitCount--; ) notifyCount--; 1 value-- ; 1

public synchronized void V() ( value++ ; if (waitcount > 0) ( not if yCount++ ; notify0 ; 1 3

Suppose multiple threads are blocked inside wait 0 in P and several calls to V barge ahead of any of the notified threads reacquiring the monitor lock. If the number of barging V calls exceeds the number of waiting threads, then a barging call to P will needlessly wait. To fix this, we change the condition that determines if a thread calls wait () inside P. Also, only as many notifies need be done as there are waiting threads; therefore, we refine the condition of the if statement in the V code. One major problem, though, remains to be fixed. Consider the following sequence of events. A thread calls P and waits because the semaphore value is 0. Then, some thread calls V. Before the notified thread reacquires the monitor lock, it is interrupted. Note that the semaphore value and the notification counter are both 1. Next, some thread calls P and it completes successfully. The semaphore value is now 0, but the notification counter remains 1. Then several threads call P and wait. Next, one of the waiting threads is in- terrupted, causing its wait call to throw an exception. Due to the catch block, the thread reenters the monitor, calls notify()) and rethrows the exception. Now, one of the remaining waiting threads reenters the monitor. Because the notification counter is 1, the thread thinks V was called and its P completes erroneously. The fix is to decrement the notification counter if it exceeds the wait set counter whenever a P is not required to call wait0. 4.7 Seventh Attempt The following Java monitor [Doug Lea, personal com- munication] implements a counting semaphore, has all of our desirable properties, and is race-condition free with respect to interrupt () , not if y 0, and barging.

public class CountingSemaphore { private int value = 0; private int waitcount = 0; private int notifyCount = 0;

public CountingSemaphore(int initial)

( if (initial > 0) value = initial; 3

public synchronized void PO throws InterruptedException C if (value <= waitcount) ( waitCount++; try < do ( waito; ) while (notifycount == 0); 1 catch(InterruptedException e) ( notify0 ; throw e; 1 finally ( waitCount--; 3 notifyCount--; 1 else C if (not if yCount > waitcount) notifyCount--; 1 value-- ; 1

public synchronized void V() ( value++ ; if (waitcount > notifycount) ( notifyCount++; notify0 ; )

5 Conclusions The above sequence of attempts at a Java monitor im- plementing a counting semaphore clearly shows the dif- ficulties in writing correctly synchronized multithreaded Java programs. The Java monitor is really a low-level synchronization tool with many subtleties and race con- ditions, in contrast to the monitor theory one learns in an operating systems class. Unfortunately, the applica- tions programmer must struggle with these problems. Java needs additional class libraries that provide less troublesome, more user-friendly synchronization tools.

6 References

PI

PI

PI

PI

Andrews, G. R. Concurrent Programming: Princi- ples and Practice. Benjamin/Cummings, 1991.

Arnold, K., and Gosling, J. The Java Programming Language, 2”d ed. Addison-Wesley, 1998.

Buhr, P. A., Fortier, M., and Coffin, M. H. Moni- tor Classification. ACM %omputing Surveys 27, 1 (March 1995).

Gosling, J., Joy, B., and Steele, G. The Java Lan- guage Specification. Addison-Wesley, 1996.