SlideShare une entreprise Scribd logo
1  sur  221
Télécharger pour lire hors ligne
Java World Articles (www.javaworld.com)




Programming Java Threads
In The Real World
Allen Hollub




                                          98
Contents
Programming Java threads in the real world, Part 1 ....................................................................4
     Platform dependence...........................................................................................................5
     Atomic energy ......................................................................................................................5
     Concurrency versus parallelism ............................................................................................7
     Get your priorities straight ...................................................................................................8
     Cooperate! .........................................................................................................................10
     Mapping kernel threads to user processes .........................................................................11
     Wrapping it up ...................................................................................................................13
     About the author ...............................................................................................................13
Programming Java threads in the real world, Part 2 ..................................................................15
     The perils of race conditions, deadlock, and other threading problems ..............................15
     Monitors, mutexes, and bathrooms ...................................................................................15
     Why is stop() deprecated in JDK 1.2? .................................................................................16
     Race conditions and spin locks ...........................................................................................17
     Working with wait() and notify() ........................................................................................17
     Threads are not objects......................................................................................................20
     The deadlock scenario ........................................................................................................22
     Get out the magnifying glass ..............................................................................................23
     Nested-monitor lockout .....................................................................................................25
     Conclusion .........................................................................................................................26
     About the author ...............................................................................................................27
     Resources ..........................................................................................................................27
Programming Java threads in the real world, Part 3 ..................................................................29
     Roll-your-own mutexes and centralized lock management ................................................29
     When 'synchronized' isn't good enough .............................................................................29
     So, roll your own ................................................................................................................30
     Semaphores .......................................................................................................................32
     Managing the locks ............................................................................................................32
     A manageable mutex .........................................................................................................35
Until next time ...................................................................................................................39
     About the author ...............................................................................................................39
Programming Java threads in the real world, Part 4 ..................................................................41
     Condition variables and counting semaphores -- filling in a few chinks in Java's threading
     model ................................................................................................................................41
     Oops! Could my code have bugs in it? ................................................................................41
     Condition variables ............................................................................................................44
     Counting semaphores ........................................................................................................52
     Wrapping up ......................................................................................................................56
     About the author ...............................................................................................................57
Programming Java threads in the real world, Part 5 ..................................................................58
     Has Sun abandoned 'run anywhere'? Plus: Threads and Swing, timers, and getting around
     stop(), suspend(), and resume() deprecation ......................................................................58
     Whatever happened to 'run anywhere'? ............................................................................58
     Back to threads ..................................................................................................................59
     Swingin' threads .................................................................................................................60
     Using the Swing Timer........................................................................................................62
     Roll your own .....................................................................................................................70
     How does an Alarm work? .................................................................................................71
     Happy trails ........................................................................................................................88
     Back to threads ..................................................................................................................89
     Swingin' threads .................................................................................................................90
     Using the Swing Timer........................................................................................................92
     Roll your own ................................................................................................................... 100
     How does an Alarm work? ............................................................................................... 102
     Happy trails ...................................................................................................................... 118
Programming Java threads in the real world, Part 6 ................................................................ 120
     The Observer pattern and mysteries of the AWTEventMulticaster ................................... 120
     Implementing Observer in a multithreaded world ............................................................ 120
     Observer-side problems: inner-class synchronization ....................................................... 121
Notifier-side problems: notifications in a multithreaded world ........................................ 124
     Mysteries of the AWTEventMulticaster ............................................................................ 133
     Wrapping up .................................................................................................................... 141
Programming Java threads in the real world, Part 7 ................................................................ 143
     Singletons, critical sections, and reader/writer locks ........................................................ 143
     Critical sections, singletons, and the Class object ............................................................. 143
     Singletons ........................................................................................................................ 146
     Reader/writer locks .......................................................................................................... 154
     It's a wrap ........................................................................................................................ 164
     About the author ............................................................................................................. 164
Programming Java threads in the real world, Part 8 ................................................................ 166
     Threads in an object-oriented world, thread pools, implementing socket 'accept' loops .. 166
     Synchronous vs. asynchronous messages ......................................................................... 166
     The thread-per-method solution ...................................................................................... 167
     Thread pools and blocking queues ................................................................................... 172
     Sockets and thread pools ................................................................................................. 183
     Conclusion ....................................................................................................................... 193
     About the author ............................................................................................................. 193
Programming Java threads in the real world, Part 9 ................................................................ 195
     More threads in an object-oriented world: Synchronous dispatchers, active objects,
     detangling console I/O ..................................................................................................... 195
     Synchronous dispatching ................................................................................................. 195
     Active objects................................................................................................................... 205
     Wrapping things up, books, and JavaOne ......................................................................... 219
     And now for something completely different... ................................................................ 219




Programming Java threads in the real world, Part 1
By Allen Holub, JavaWorld.com, 10/01/98
All Java programs other than simple console-based applications are multithreaded, whether
you like it or not. The problem is that the Abstract Windowing Toolkit (AWT) processes
operating system (OS) events on its own thread, so your listener methods actually run on the
AWT thread. These same listener methods typically access objects that are also accessed from
the main thread. It may be tempting, at this point, to bury your head in the sand and pretend
you don't have to worry about threading issues, but you can't usually get away with it. And,
unfortunately, virtually none of the books on Java addresses threading issues in sufficient
depth. (For a list of helpful books on the topic, see Resources.)

This article is the first in a series that will present real-world solutions to the problems of
programming Java in a multithreaded environment. It's geared to Java programmers who
understand the language-level stuff (the synchronized keyword and the various facilities of the
Thread class), but want to learn how to use these language features effectively.


Platform dependence

Unfortunately, Java's promise of platform independence falls flat on its face in the threads
arena. Though it's possible to write a platform-independent multithreaded Java program, you
have to do it with your eyes open. This isn't really Java's fault; it's almost impossible to write a
truly platform-independent threading system. (Doug Schmidt's ACE [Adaptive Communication
Environment] framework is a good, though complex, attempt. See Resources for a link to his
program.) So, before I can talk about hard-core Java-programming issues in subsequent
installments, I have to discuss the difficulties introduced by the platforms on which the Java
virtual machine (JVM) might run.

Atomic energy

The first OS-level concept that's important to understand is atomicity. An atomic operation
cannot be interrupted by another thread. Java does define at least a few atomic operations. In
particular, assignment to variables of any type except long or double is atomic. You don't have to
worry about a thread preempting a method in the middle of the assignment. In practice, this
means that you never have to synchronize a method that does nothing but return the value of
(or assign a value to) a boolean or int instance variable. Similarly, a method that did a lot of
computation using only local variables and arguments, and which assigned the results of that
computation to an instance variable as the last thing it did, would not have to be synchronized.
For example:

class some_class
{
   int some_field;
   void f( some_class arg ) // deliberately not synchronized
   {
     // Do lots of stuff here that uses local variables
// and method arguments, but does not access
        // any fields of the class (or call any methods
        // that access any fields of the class).
        // ...
        some_field = new_value; // do this last.
    }
}



On the other hand, when executing x=++y or x+=y, you could be preempted after the increment
but before the assignment. To get atomicity in this situation, you'll need to use the keyword
synchronized.

All this is important because the overhead of synchronization can be nontrivial, and can vary
from OS to OS. The following program demonstrates the problem. Each loop repetitively calls a
method that performs the same operations, but one of the methods (locking()) is synchronized
and the other (not_locking()) isn't. Using the JDK "performance-pack" VM running under Windows
NT 4, the program reports a 1.2-second difference in runtime between the two loops, or about
1.2 microseconds per call. This difference may not seem like much, but it represent a 7.25-
percent increase in calling time. Of course, the percentage increase falls off as the method does
more work, but a significant number of methods -- in my programs, at least -- are only a few
lines of code.

import java.util.*;
class synch
{
  synchronized int locking (int a, int b){return a + b;}
   int       not_locking (int a, int b){return a + b;}

    private static final int ITERATIONS = 1000000;
    static public void main(String[] args)
    {
       synch tester = new synch();
       double start = new Date().getTime();
      for(long i = ITERATIONS; --i >= 0 ;)
         tester.locking(0,0);
       double end = new Date().getTime();
       double locking_time = end - start;
       start = new Date().getTime();
      for(long i = ITERATIONS; --i >= 0 ;)
         tester.not_locking(0,0);
       end = new Date().getTime();
       double not_locking_time = end - start;
       double time_in_synchronization = locking_time - not_locking_time;
       System.out.println( "Time lost to synchronization (millis.): "
                 + time_in_synchronization );
       System.out.println( "Locking overhead per call: "
                 + (time_in_synchronization / ITERATIONS) );
       System.out.println(
         not_locking_time/locking_time * 100.0 + "% increase" );
}
}



Though the HotSpot VM is supposed to address the synchronization-overhead problem,
HotSpot isn't a freebee -- you have to buy it. Unless you license and ship HotSpot with your app,
there's no telling what VM will be on the target platform, and of course you want as little as
possible of the execution speed of your program to be dependent on the VM that's executing it.
Even if deadlock problems (which I'll discuss in the next installment of this series) didn't exist,
the notion that you should "synchronize everything" is just plain wrong-headed.

Concurrency versus parallelism

The next OS-related issue (and the main problem when it comes to writing platform-
independent Java) has to do with the notions of concurrency and parallelism. Concurrent
multithreading systems give the appearance of several tasks executing at once, but these tasks
are actually split up into chunks that share the processor with chunks from other tasks. The
following figure illustrates the issues. In parallel systems, two tasks are actually performed
simultaneously. Parallelism requires a multiple-CPU system.




Unless you're spending a lot of time blocked, waiting for I/O operations to complete, a program
that uses multiple concurrent threads will often run slower than an equivalent single-threaded
program, although it will often be better organized than the equivalent single-thread version. A
program that uses multiple threads running in parallel on multiple processors will run much
faster.

Page 2 of 6
Though Java permits threading to be implemented entirely in the VM, at least in theory, this
approach would preclude any parallelism in your application. If no operating-system-level
threads were used, the OS would look at the VM instance as a single-threaded application,
which would most likely be scheduled to a single processor. The net result would be that no
two Java threads running under the same VM instance would ever run in parallel, even if you
had multiple CPUs and your VM was the only active process. Two instances of the VM running
separate applications could run in parallel, of course, but I want to do better than that. To get
parallelism, the VM must map Java threads through to OS threads; so, you can't afford to ignore
the differences between the various threading models if platform independence is important.

Get your priorities straight

I'll demonstrate the ways the issues I just discussed can impact your programs by comparing
two operating systems: Solaris and Windows NT.

Java, in theory at least, provides ten priority levels for threads. (If two or more threads are both
waiting to run, the one with the highest priority level will execute.) In Solaris, which supports
231 priority levels, this is no problem (though Solaris priorities can be tricky to use -- more on
this in a moment). NT, on the other hand, has seven priority levels available, and these have to
be mapped into Java's ten. This mapping is undefined, so lots of possibilities present
themselves. (For example, Java priority levels 1 and 2 might both map to NT priority level 1, and
Java priority levels 8, 9, and 10 might all map to NT level 7.)

NT's paucity of priority levels is a problem if you want to use priority to control scheduling.
Things are made even more complicated by the fact that priority levels aren't fixed. NT provides
a mechanism called priority boosting, which you can turn off with a C system call, but not from
Java. When priority boosting is enabled, NT boosts a thread's priority by an indeterminate
amount for an indeterminate amount of time every time it executes certain I/O-related system
calls. In practice, this means that a thread's priority level could be higher than you think
because that thread happened to perform an I/O operation at an awkward time.

The point of the priority boosting is to prevent threads that are doing background processing
from impacting the apparent responsiveness of UI-heavy tasks. Other operating systems have
more-sophisticated algorithms that typically lower the priority of background processes. The
downside of this scheme, particularly when implemented on a per-thread rather than a per-
process level, is that it's very difficult to use priority to determine when a particular thread will
run.

It gets worse.

In Solaris, as is the case in all Unix systems, processes have priority as well as threads. The
threads of high-priority processes can't be interrupted by the threads of low-priority processes.
Moreover, the priority level of a given process can be limited by a system administrator so that
a user process won't interrupt critical OS processes. NT supports none of this. An NT process is
just an address space. It has no priority per se, and is not scheduled. The system schedules
threads; then, if a given thread is running under a process that isn't in memory, the process is
swapped in. NT thread priorities fall into various "priority classes," that are distributed across a
continuum of actual priorities. The system looks like this:




                                Windows NT's priority architecture

The columns are actual priority levels, only 22 of which must be shared by all applications. (The
others are used by NT itself.) The rows are priority classes. The threads running in a process
pegged at the idle priority class are running at levels 1 through 6 and 15, depending on their
assigned logical priority level. The threads of a process pegged as normal priority class will run
at levels 1, 6 through 10, or 15 if the process doesn't have the input focus. If it does have the
input focus, the threads run at levels 1, 7 through 11, or 15. This means that a high-priority
thread of an idle priority class process can preempt a low-priority thread of a normal priority
class process, but only if that process is running in the background. Notice that a process
running in the "high" priority class only has six priority levels available to it. The other classes
have seven.

NT provides no way to limit the priority class of a process. Any thread on any process on the
machine can take over control of the box at any time by boosting its own priority class; there is
no defense against this.

The technical term I use to describe NT's priority is unholy mess. In practice, priority is virtually
worthless under NT.

So what's a programmer to do? Between NT's limited number of priority levels and it's
uncontrollable priority boosting, there's no absolutely safe way for a Java program to use
priority levels for scheduling. One workable compromise is to restrict yourself to
Thread.MAX_PRIORITY, Thread.MIN_PRIORITY, and Thread.NORM_PRIORITY when you call setPriority(). This
restriction at least avoids the 10-levels-mapped-to-7-levels problem. I suppose you could use
the os.name system property to detect NT, and then call a native method to turn off priority
boosting, but that won't work if your app is running under Internet Explorer unless you also use
Sun's VM plug-in. (Microsoft's VM uses a nonstandard native-method implementation.) In any
event, I hate to use native methods. I usually avoid the problem as much as possible by putting
most threads at NORM_PRIORITY and using scheduling mechanisms other than priority. (I'll
discuss some of these in future installments of this series.)
Cooperate!

There are typically two threading models supported by operating systems: cooperative and
preemptive.

The cooperative multithreading model
In a cooperative system, a thread retains control of its processor until it decides to give it up
(which might be never). The various threads have to cooperate with each other or all but one of
the threads will be "starved" (meaning, never given a chance to run). Scheduling in most
cooperative systems is done strictly by priority level. When the current thread gives up control,
the highest-priority waiting thread gets control. (An exception to this rule is Windows 3.x, which
uses a cooperative model but doesn't have much of a scheduler. The window that has the focus
gets control.)

The main advantage of cooperative multithreading is that it's very fast and has a very low
overhead. For example, a context swap -- a transfer of control from one thread to another --
can be performed entirely by a user-mode subroutine library without entering the OS kernel.
(In NT, which is something of a worst-case, entering the kernel wastes 600 machine cycles. A
user-mode context swap in a cooperative system does little more than a C setjump/longjump call
would do.) You can have thousands of threads in your applications significantly impacting
performance. Since you don't lose control involuntarily in cooperative systems, you don't have
to worry about synchronization either. That is, you never have to worry about an atomic
operation being interrupted. The main disadvantage of the cooperative model is that it's very
difficult to program cooperative systems. Lengthy operations have to be manually divided into
smaller chunks, which often must interact in complex ways.

Page 4 of 6

The preemptive multithreading model
The alternative to a cooperative model is a preemptive one, where some sort of timer is used
by the operating system itself to cause a context swap. The interval between timer ticks is
called a time slice. Preemptive systems are less efficient than cooperative ones because the
thread management must be done by the operating-system kernel, but they're easier to
program (with the exception of synchronization issues) and tend to be more reliable since
starvation is less of a problem. The most important advantage to preemptive systems is
parallelism. Since cooperative threads are scheduled by a user-level subroutine library, not by
the OS, the best you can get with a cooperative model is concurrency. To get parallelism, the OS
must do the scheduling. Of course, four threads running in parallel will run much faster than the
same four threads running concurrently.

Some operating systems, like Windows 3.1, only support cooperative multithreading. Others,
like NT, support only preemptive threading. (You can simulate cooperative threading in NT with
a user-mode library like the "fiber" library, but fibers aren't fully integrated into the OS.) Solaris
provides the best (or worst) of all worlds by supporting both cooperative and preemptive
models in the same program.

Mapping kernel threads to user processes

The final OS issue has to do with the way in which kernel-level threads are mapped into user-
mode processes. NT uses a one-to-one model, illustrated in the following picture.




NT user-mode threads effectively are kernel threads. They are mapped by the OS directly onto
a processor and they are always preemptive. All thread manipulation and synchronization are
done via kernel calls (with a 600-machine-cycle overhead for every call). This is a
straightforward model, but is neither flexible nor efficient.

The Solaris model, pictured below, is more interesting. Solaris adds to the notion of a thread,
the notion of a lightweight process (LWP). The LWP is a schedulable unit on which one or more
threads can run. Parallel processing is done on the LWP level. Normally, LWPs reside in a pool,
and they are assigned to particular processors as necessary. An LWP can be "bound" to a
specific processor if it's doing something particularly time critical, however, thereby preventing
other LWPs from using that processor.

Up at the user level, you have a system of cooperative, or "green," threads. In a simple
situation, a process will have one LWP shared by all the green threads. The threads must yield
control to each other voluntarily, but the single LWP the threads share can be preempted by an
LWP in another process. This way the processes are preemptive with respect to each other (and
can execute in parallel), but the threads within the process are cooperative (and execute
concurrently).

A process isn't limited to a single LWP, however. The green threads can share a pool of LWPs in
a single process. The green threads can be attached (or "bound") to an LWP in two ways:

Page 5 of 6

   1. The programmer explicitly "binds" one or more threads to a specific LWP. In this case,
      the threads sharing a LWP must cooperate with each other, but they can preempt (or be
      preempted by) threads bound to a different LWP. If every green thread was bound to a
      single LWP, you'd have an NT-style preemptive system.

   2. The threads are bound to green threads by the user-mode scheduler. This is something
      of a worst case from a programming point of view because you can't assume a
      cooperative or a preemptive environment. You may have to yield to other threads if
      there's only one LWP in the pool, but you might also be preempted.
This threading model gives you an enormous amount of flexibility. You can choose between an
extremely fast (but strictly concurrent) cooperative system, a slower (but parallel) preemptive
system, or any combination of the two.

So why does this matter to a Java programmer? The main issue is that the choice of threading
model is entirely up to the VM -- you have no control. For example, early versions of the Solaris
VM were strictly cooperative. Java threads were all green threads sharing a single LWP. The
current version of the Solaris VM, however, uses several LWPs. Similarly, the NT VMs don't have
the equivalent of green threads, so they're always preemptive. In order to write platform-
independent code, you must make two seemingly contradictory assumptions:

   1. You can be preempted by another thread at any time. You must use the synchronized
      keyword carefully to assure that non-atomic operations work correctly.

   2. You will never be preempted unless you give up control. You must occasionally perform
      some operation that will give control to other threads so they can have a chance to run.
      Use yield() and sleep() in appropriate places (or make blocking I/O calls). For example, you
      might want to consider calling yield() every one hundred iterations or so of a long loop,
      or voluntarily going to sleep for a few milliseconds every so often to give lower-priority
      threads a chance to run. (yield() will yield control only to threads running at your priority
      level or higher).



Wrapping it up

So, those are the main OS-level issues you must consider when you're writing a Java program.
Since you can make no assumptions about your operating environment, you have to program
for the worst case. For example, you have to assume you can be preempted at any time, so you
must use synchronized appropriately, but you must also assume that you will never be
preempted, so you must also use yield(), sleep(), or occasionally blocking I/O calls to permit other
threads to run. You can't assume priority levels 1 and 2 are different. They might not be after
NT has mapped Java's 10 levels into its 7 levels. You can't assume that a priority level 2 thread
will always be higher priority than one that runs at level 1.

Subsequent articles will get into considerable detail about various thread-related programming
problems and solutions. Here's the roadmap for the rest of the series:

About the author

Allen Holub has been working in the computer industry since 1979. He is widely published in
magazines (Dr. Dobb's Journal, Programmers Journal, Byte, and MSJ, among others). He has
seven books to his credit, and is currently working on an eighth that will present the complete
sources for a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now
looks at C++ as a bad dream, the memory of which is mercifully fading. He's been teaching
programming (first C, then C++ and MFC, now OO-Design and Java) both on his own and for the
University of California, Berkeley Extension, since 1982. Allen offers both public classes and in-
house training in Java and object-oriented design topics. He also does object-oriented design
consulting. Get information, and contact Allen, via his Web site http://www.holub.com.



Page 6 of 6

   1.   Deadlock, starvation, and nested-monitor lockout
   2.   Roll-your-own mutexes and a deadlock-handling lock manager
   3.   Counting semaphores, condition variables, and singletons
   4.   Event notification in a multithreaded environment (the mysteries of the
        AWTEventMulticaster)
   5.   Reader/writer locks
   6.   Timers
   7.   Synchronous-dispatching: multithreading without threads
   8.   Implementing the active-object pattern

Resources


        For a great in-depth look at multithreading in general and the implementation of
        multithreading both in and with Java in particular, this one's a must. It's required
        reading if you're using threads heavilyDoug Lea, Concurrent Programming in JavaDesign
        Principles and Patterns (Addison Wesley, 1997). http://java.sun.com/docs/books/cp/
        For an intro-level book on Java threading that is less technical but more readable than
        Lea's effort, seeScott Oaks and Henry Wong, Java Threads (O'Reilly, 1997)
        http://www.oreilly.com/catalog/jthreads/
        This book is good for those looking into the general subject of multithreading, but
        doesn't have a Java slantBill Lewis and Daniel J. Berg, Threads PrimerA Guide to
        Multithreaded Programming (Prentice Hall/SunSoft Press, ISBN 0-13-443698-9)
        http://www.sun.com/books/books/Lewis/Lewis.html
        Doug Schmidt's ACE framework is a good, though complex, attempt at a truly platform-
        independent threading system
        http://www.cs.wustl.edu/~schmidt/
Programming Java threads in the real world, Part 2

The perils of race conditions, deadlock, and other threading problems



In his recent " Design for thread safety" article (part of the Design Techniques column), Bill
Venners introduced the notion of a race condition, a situation whereby two threads
simultaneously contend for the same object and, as a consequence, leave the object in an
undefined state. Bill pointed out that Java's synchronized keyword is in the language to help avoid
these problems, and he provided a straightforward example of its use. Last month's article was
the first in a series on threads. The present article continues that series, covering race
conditions in depth. It also discusses various deadlock scenarios, which are related to race
conditions but are much harder to find and debug. This month, we'll focus primarily on the
problems encountered when programming Java threads. Subsequent Java Toolbox columns will
focus entirely on solutions.

Monitors, mutexes, and bathrooms

Just to make sure we're all starting from the same place, a little review of terms is in order. The
central concept for synchronization in the Java model is the monitor, developed some 20 years
ago by C. A. R. Hoare. A monitor is a body of code guarded by a mutual-exclusion semaphore
(or, to use a term coined at Digital Equipment Corp., a mutex). The central notion of a mutex
concerns ownership. Only one thread can own the mutex at a time. If a second thread tries to
"acquire" ownership, it will block (be suspended) until the owning thread "releases" the mutex.
If several threads are all waiting to acquire the same mutex, they will all be released
simultaneously when the owning thread releases the mutex. The released threads will then
have to sort out amongst themselves who gains ownership. (Typically, priority order, FIFO
order, or some combination thereof is used to determine which thread gains control.) You
guard a block of code by acquiring a mutex at the top of the block and releasing it at the
bottom. The code comprising the monitor does not have to be contiguous: several
discontiguous code blocks can all acquire and release the same mutex, in which case all of this
code is considered to be in the same monitor because it uses a common mutex.

The best analogy I've heard for a monitor is an airplane bathroom. Only one person can be in
the bathroom at a time (we hope). Everybody else is queued up in a rather narrow aisle waiting
to use it. As long as the door is locked, the bathroom is unaccessible. Given these terms, in our
analogy the object is the airplane, the bathroom is the monitor (assuming there's only one
bathroom), and the lock on the door is the mutex.

In Java, every object has one and only one monitor and mutex associated with it. The single
monitor has several doors into it, however, each indicated by the synchronized keyword. When a
thread passes over the synchronized keyword, it effectively locks all the doors. Of course, if a
thread doesn't pass across the synchronized keyword, it hasn't locked the door, and some other
thread is free barge in at any time.

Bear in mind that the monitor is associated with the object, not the class. Several threads can
all be executing the same method in parallel, but the receiving objects (as identified by the this
references) have to be different. For example, several instances of a thread-safe queue could
be in use by several threads. These threads could simultaneously enqueue objects to different
queues, but they could not enqueue to the same queue at the same time. Only one thread at a
time can be in the monitor for a given queue.

Page 2 of 7

To refine the earlier analogy, the airplane is still the object, but the monitor is really all the
bathrooms combined (each synchronized chunk of code is a bathroom). When a thread enters
any bathroom, the doors on all the bathrooms are locked. Different instances of a class are
different airplanes, however, and if the bathroom doors in your airplane are unlocked, you
needn't really care about the state of the doors in the other airplanes.

Why is stop() deprecated in JDK 1.2?

The fact that the monitor is built into every Java object is actually somewhat controversial.
Some of the problems associated with bundling the condition variable and mutex together
inside every Java object have been fixed in JDK 1.2, by the simple expediency of deprecating the
most problematic methods of the Thread class: stop() and suspend(). You can deadlock a thread if
you call either of these methods from inside a synchronized method of your own. Look at the
following method, remembering that the mutex is the lock on the door, and the thread that
locks the door "owns" the monitor. That's why the stop() and suspend() methods are deprecated
in JDK 1.2. Consider this method:

class some_class
{ //...
   synchronized void f()
   { Thread.currentThread().stop();
   }
}



Now consider what happens when a thread calls f(). The thread acquires the lock when entering
the monitor but then stops. The mutex is not released by stop(). This is the equivalent of
someone going into the bathroom, locking the door, and flushing himself down the toilet! Any
other thread that now tries to call f() (or any other synchronized method of the class) on the
same object is blocked forever. The suspend() method (which is also deprecated) has the same
problem. The sleep() method (which is not deprecated) can be just as tricky. (Someone goes into
the bathroom, locks the door, and falls asleep). Also remember that Java objects, even those
that extend Thread or implement Runnable, continue to exist, even if the associated thread has
stopped. You can indeed call a synchronized method on an object associated with a stopped
thread. Be careful.

Race conditions and spin locks

A race condition occurs when two threads try to access the same object at the same time, and
the behavior of the code changes depending on who wins. The following diagram shows a
single (unsynchronized) object accessed simultaneously by multiple threads. A thread can be
preempted in fred() after modifying one field but before modifying the other. If another thread
comes along at that point and calls any of the methods shown, the object will be left in an
unstable state, because the initial thread will eventually wake up and modify the other field.




Usually, you think of objects sending messages to other objects. In multithreaded
environments, you must think about message handlers running on threads. Think: this thread
causes this object to send a message to that object. A race condition can occur when two
threads cause messages to be sent to the same object at the same time.

Page 3 of 7

Working with wait() and notify()

The following code demonstrates a blocking queue used for one thread to notify another when
some event occurs. (We'll see a more realistic version of these in a future article, but for now I
want to keep things simple.) The basic notion is that a thread that tries to dequeue from an
empty queue will block until some other thread puts something in the queue.

class Notifying_queue
{
   private static final queue_size = 10;
   private Object[] queue = new Object[ queue_size ];
   private int     head = 0;
   private int     tail = 0;
   public void synchronized enqueue( Object item )
   {
     queue[++head %= queue_size ]= item;
     this.notify();            // The "this" is there only to
   }                      // improve readability.
   public Object synchronized dequeue( )
   {
     try
     { if( head == tail ) // <=== This is a bug
           this.wait();
     }
     catch( InterruptedException e )
     {
        // If we get here, we were not actually notified.
        // returning null doesn't indicate that the
        // queue is empty, only that the waiting was
        // abandoned.
        return null;
     }
     return queue[++tail %= queue_size ];
   }
}



Starting with an empty queue, let's follow the sequence of operations in play if one thread does
a dequeue and then another (at some later time) enqueues an item.

    1. The dequeueing thread calls dequeue(), entering the monitor (and locking out other
       threads) until the monitor is released. The thread tests for an empty queue
       (head==tailwait(). (I'll explain why this is a bug in a moment.)

    2. The wait() releases the lock. (The current thread temporarily leaves the monitor.) It then
       blocks on a second synchronization object called a condition variable. (The basic notion
       of a condition variable is that a thread blocks until some condition becomes true. In the
       case of Java's built-in condition variable, the thread will wait until the notified condition
       is set to true by some other thread calling notify.) It's important to realize that the
       waiting thread has released the monitor at this juncture.

    3. A second thread now comes along and enqueues an object, eventually calling notify(),
       thereby releasing the waiting (dequeueing) thread.
4. The dequeueing thread now has to wait to reacquire the monitor before wait() can
       return, so it blocks again, this time on the lock associated with the monitor.

    5. The en queueing thread returns from the enqueue() method releasing the monitor.

    6. The dequeueing thread acquires the monitor, wait() returns, an object is dequeued, and
       dequeue() returns, releasing the monitor.




Race conditions following a wait()
Now, what sorts of problems can arise? The main ones are unexpected race conditions. First,
what if you replaced the notify() call with a call to notifyAll()? Imagine that several threads were
simultaneously trying to dequeue something from the same, empty, queue. All of these threads
are blocked on a single condition variable, waiting for an enqueue operation to be executed.
When enqueue() sets the condition to true by calling notifyAll(), the threads are all released from
the wait (moved from a suspended to a runnable state). The released threads all try to acquire
the monitor at once, but only one would "win" and get the enqueued object. The problem is
that the others would then get the monitor too, in some undefined sequence, after the winning
thread had dequeued the object and returned from dequeue(). Since these other threads don't
retest for an empty queue, they will dequeue garbage. (Looking at the code, the dequeue()
method will advance the tail pointer to the next slot, the contents of which are undefined since
the queue is empty.)



Page 4 of 7

Fix the problem by replacing the if statement with a while loop (called a spin lock). Now the
threads that don't get the object will go back to waiting:

  public Object synchronized dequeue( )
  {
    try
    { while( head == tail ) // used to be an if
         this.wait();
    }
    catch( InterruptedException e )
    { return null;
    }
    return queue[++tail %= queue_size ];
  }
That while loop also solves another, less obvious problem. What if we leave the notify()
statement in place, and don't put in a notifyAll()? Since notify() releases only one waiting thread,
won't that solve the problem? It turns out that the answer is no. Here's what can happen:

    1. notify() is called by the enqueueing thread, releasing the condition variable.

    2. The dequeueing thread is then preempted, after being released from the wait on the
       condition variable, but before it tries to reacquire the monitor's lock.

    3. A second thread calls dequeue() at exactly this point, and successfully enters the monitor
       because no other threads are officially waiting. This second thread successfully
       dequeues the object; wait() is never called since the queue isn't empty.

    4. The original thread is now allowed to run, it acquires the monitor, doesn't test for
       empty a second time, and then dequeues garbage.



This second scenario is easily fixed the same way as the first: replace the if statement with a
while loop.

Note that the Java specification does not require that wait() be implemented as an atomic
operation (that is, one that can't be preempted while moving from the condition variable to the
monitor's lock). This means that using an if statement instead of a while loop might work in
some Java implementations, but the behavior is really undefined. Using a spin lock instead of a
simple if statement is cheap insurance against implementations that don't treat wait() as atomic.

Threads are not objects

Now let's move on to harder-to-find problems. The first difficulty is the commonplace confusion
of threads and objects: Methods run on threads, objects do not. Put another way, the only way
to get a method to run on a given thread is to call it (either directly or indirectly) from that
thread's run() method. Simply putting it into the Thread derivative is not enough. For example,
look at the following simple thread (which just prints its fields every so often):

class My_thread extends Thread
{
   private int field_1 = 0;
   private int field_2 = 0;
   public void run()
   {
     setDaemon(true); // this thread will not keep the app alive
     while( true )
     {
        System.out.println( " field_1=" + field_1
                      " field_2=" + field_2 );
sleep(100);
      }
    }
    synchronized public void modify( int new_value )
    { field_1 = new_value;
         field_2 = new_value;
    }
}



You could start up the thread and send it a message like this:

My_thread test = new My_thread;
test.start();
//...
test.modify(1);



The only functions that run on the new thread are run() itself and println() (which run() calls). The
modify() method never runs on the same thread as the println call; rather, it runs on whatever
thread was running when the call was made. (In this case, it runs on whatever thread main() is
running on.) Depending on timing, the earlier fragment could print:

Page 5 of 7

field_1=0, field2=0



But it could just as easily print:

field_1=0, field2=1



or

field_1=1, field2=1



There's just no telling. (In the first and last cases, the thread would have been outside the println
statement when modify() was called. In the second example, the thread would have been
halfway through evaluating the arguments to println(), having fetched field_1, but not field_2. It
prints the unmodified field_1 and the modified field_2.

The main issue here is that there is no simple solution to this problem. The modify() method is
indeed synchronized in the earlier example, but run() can't be. Were it synchronized, you'd enter
the monitor (and lock the object), when you started up the thread. Thereafter, any other thread
that called any synchronized method on the object (such as modify()) would block until the
monitor was released. Since run() doesn't return (as is often the case), the release will never
happen, and the thread will act like a black hole, sucking up any other thread that calls any of
its synchronized methods. In the current example, the main thread would be suspended, and
the program would hang. So just using the synchronized keyword in a naive way gets us nowhere.

The deadlock scenario

Synchronizing run() is a good example of a simple deadlock scenario, where a thread is blocked
forever, waiting for something to happen that can't. Let's look at a few examples that are more
realistic than this.

The most common deadlock scenario occurs when two threads are both waiting for each other
to do something. The following (admittedly contrived) code snippet makes what's going on
painfully obvious:

class Flintstone
{
   int field_1; private Object lock_1 = new int[1];
   int field_2; private Object lock_2 = new int[1];
   public void fred( int value )
   { synchronized( lock_1 )
     { synchronized( lock_2 )
         {
           field_1 = 0;
           field_2 = 0;
         }
     }
   }
   public void barney( int value )
   { synchronized( lock_2 )
     { synchronized( lock_1 )
         {
           field_1 = 0;
           field_2 = 0;
         }
     }
   }
}



Now, imagine a scenario whereby one thread (call it Wilma) calls fred(), passes through the
synchronization of lock_1, and is then preempted, allowing another thread (call it Betty) to
execute. Betty calls barney(), acquires lock_2, and tries to acquire lock_1, but can't because Wilma
has it. Betty is now blocked, waiting for lock_1 to become available, so Wilma wakes up and tries
to acquire lock_2 but can't because Betty has it. Wilma and Betty are now deadlocked. Neither
one can ever execute.
(Note that lock_1 and lock_2 have to be one-element arrays rather than simple ints, because only
objects have monitors in Java; the argument to synchronized must be an object. An array is a first-
class object in Java; a primitive-type such as int is not. Consequently, you can synchronize on it.
Moreover, a one-element array is efficient to bring into existence compared to a more
elaborate object (like an Integer) since it's both small and does not require a constructor call.
Also, note that I can keep the reference to the lock as a simple Object reference, since I'll never
access the array elements.)

Page 6 of 7

As I mentioned above, Wilma and Betty are a contrived example, but the multiple-lock situation
comes up frequently. I'll give a more detailed example next month.

Get out the magnifying glass

If all deadlock scenarios were as easy to recognize as Wilma and Betty, deadlock wouldn't be a
problem. Consider the following code, though:

class Boss
{
   private Side_kick robin;
   public synchronized void set_side_kick( Side_kick kid_in_tights )
   { robin = kid_in_tights;
   };
   public synchronized void to_the_bat_cave()
   { robin.get_in_the_car();
   }
   public synchronized void okay() // sent to us by robin
   { //...
   }
   public synchronized void hold_on() // sent to us by robin
   { //...
   }
}
//-------------------------------------------------------
class Side_kick
{
   private Boss batman;
   public synchronized void set_boss( Boss guy_in_cape )
   { batman = guy_in_cape;
   }
   public synchronized void get_in_the_car() // sent by batman
   { batman.okay();
   }
   public synchronized void sock_bam_pow() // sent from outside
   { batman.hold_on();
   }
}
//-------------------------------------------------------
class Gotham_city
{ static Boss batman = new Boss();
   static Side_kick robin = new Side_kick();
   public static void main( String[] args )
   {
     batman.set_side_kick( robin );
     robin.set_boss( batman );
     // spawn off a bunch of threads that use batman and robin.
   }
}



Now imagine the following:

    1. One thread (call it Alfred) issues a to_the_bat_cave() request to the batman object passed to
       it from main().

    2. The batman object starts to process the method, but is preempted just before it calls
       robin.get_in_the_car(). At this juncture, Alfred has acquired the lock for the batman object.

    3. Now along comes a second thread (call it Joker), which issues a sock_bam_pow() message
       to the robin object that it got from main().

    4. The robin object (whose sock_bam_pow() method is running on the Joker thread) tries to
       send a hold_on() message to batman, but can't because Alfred owns the lock on batman. So
       the Joker thread is now blocked, waiting for Alfred to release the lock on batman.

    5. Now Alfred gets a chance to run, and it tries to send a get_in_the_car() message to the
       robin object, but it can't because the Joker thread owns the lock on robin. Both threads
       are now deadlocked (sound familiar?)



Remember: threads own the locks and methods execute on threads, not objects.

This situation is, of course, much harder to see than the Wilma-and-Betty problem because the
locks in the batman-robin example are the natural locks associated with individual objects. There
are no standalone synchronized statements in the batman-robin code, and the locks are
associated with two completely distinct objects.

Multithreaded programmers tear their hair out looking for the causes of these sorts of
problems, and there are only two solutions. The first is to thoroughly design the code before
implementing it, and then to really study both the design and the implementation before you
even think about running it the first time. When I do multithreaded programs, I spend much
more time on code and design reviews than I spend on coding.
Page 7 of 7

The other solution is to use an architecture that tends to minimize these sorts of problems.
(Various threading architectures will be the subject of the remaining articles in this series.)

There is no magic bullet there. I've seen ads for a couple of products that instrument a VM in a
way that, in theory, will detect deadlock and race conditions. The problem, though, is a classic
Heisenberg-uncertainty dilemma: there's no way to observe the process without impacting it. If
the problem is timing-related, adding a print statement or changing to a debugging version of
the VM will change the timing, perhaps eliminating the problem. I haven't actually used any of
these products yet, but I remain skeptical.

Nested-monitor lockout

Another important form of deadlock is not discussed much in the Java-language books: nested-
monitor lockout. This problem usually occurs when you call a blocking function from within a
synchronized method, and the only way to release the block is to call another synchronized
method. The following code demonstrates the problem.

class Notifying_queue
{ // Same class as was described earlier, blocks on dequeue from
   // an empty queue.
   //...
}
class Black_hole
{
   private Notifying_queue queue = new Notifying_queue(5);
   public synchronized void put( Object thing )
   { queue.enqueue( thing );
   }
   public synchronized Object get( )
   { return queue.dequeue();
   }
}



Consider what happens when you try to dequeue something from an empty queue:

    1. You call get() to get the item from the queue.

    2. get() is synchronized, so the Black_hole is now locked.

    3. get() calls dequeue(), which blocks, waiting for some other thread to enqueue something.
       get() does not return.
4. Another thread tries to enqueue something, but the only way to enqueue something is
       by calling put, which we can't do because the Black_hole is locked. That is, any thread that
       tries to put() will block because the first thread has not returned from get() yet.



The Black_hole now sucks up all threads that try to put() or get() anything. They all block forever.

Depending on where this occurs, the black hole could suck up every thread in your program.
Also bear in mind that this problem can occur anytime you have a blocking call (such as a file
read) inside a synchronized method.

The only cures are:

    1. Don't make blocking calls in synchronized methods

    2. Make sure there's a way to talk to the blocking object via another class or a
       nonsynchronized method



In the current situation, you could just not synchronize put(), but that wouldn't work in a more
realistic situation where put() accessed fields of the class that were accessed by other methods.

This problem has been known since Java 1.0 was in the early prerelease stage, and several
people complained vociferously about it. (The problem is a direct result of the way Java's
synchronization works -- the condition variable and mutex are both part of the object and not
separate entities --- compounded by the fact that you have to acquire the mutex to wait on the
condition.) But as Doug Lea pointed out in a recent e-mail to me:

[the complaints] boiled down to "you tend to like best what you are most used to." Java makes
some things that are painful in POSIX easy, and vice-versa. In any case, it is pretty easy to
simulate one set of primitives with the other.



That's life, I guess.

The next several articles in this series on threads will present a solution to the problem that
decouples the semaphores from the things they guard, but that solution introduces a whole set
of additional problems.

Conclusion
Hopefully, I've demonstrated by now that programming in a multithreaded environment isn't as
easy as the evangelist types would have you believe. Java provides platform-independent ways
to use the two essential synchronization mechanisms: exclusion semaphores and condition
variables. It does it in an awkward way, however, that doesn't help much when you're trying to
do more than blink your logo in a simple applet. All is not lost, however. Over the next few
months, I'll present a library of classes that solve many common threading problems, including
some of the ones I've just discussed. Sometimes, I even think of it as fun, but maybe that's
because I've been programming too long.

About the author

Allen Holub has been working in the computer industry since 1979. He is widely published in
magazines (Dr. Dobb's Journal, Programmers Journal, Byte, MSJ, among others). He has seven
books to his credit, and is currently working on an eighth that will present the complete sources
for a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now looks at
C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming
(first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University of
California Berkeley Extension since 1982. Allen offers both public classes and in-house training
in Java and object-oriented design topics. He also does object-oriented design consulting. Get
more information and contact Allen via his Web site http://www.holub.com.

Resources


       Sun's Technical Articles page has several articles on multithreading
       http://developer.javasoft.com/developer/technicalArticles/#thread
       Prashant Jain and Douglas C. Schmidt have a good article contrasting C++ to Java that
       discusses many of the thread-related problems inherent in the language. The article can
       be found at http://www.cs.wustl.edu/%7Eschmidt/C++2java.html
       Doug Lea has a bunch of Mutex and Condition-variable classes in his util.concurrent
       package. See
       http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
       Doug Schmidt's Ace Framework is a good, though complex, attempt at a truly platform-
       independent threading system http://www.cs.wustl.edu/~schmidt/
       There are several good books that discuss the Java threading issues mentioned in the
       first article in this series. For convenience, I've listed them again here:
       For a great in-depth look at multithreading in general and the implementation of
       multithreading both in and with Java in particular, this is a must. It's required reading if
       you're using threads heavilyDoug Lea, Concurrent Programming in JavaDesign Principles
       and Patterns (ReadingAddison Wesley, 1997) http://java.sun.com/docs/books/cp/
       For a book on Java threading that is less technical but more readable than Lea's,
       seeScott Oaks and Henry Wong, Java Threads (Sebastopol, Calif.O'Reilly, 1997)
       http://www.oreilly.com/catalog/jthreads/
This book is good for the general subject of multithreading but doesn't have a Java
slantBill Lewis and Daniel J. Berg, Threads PrimerA Guide to Multithreaded Programming
(Englewood CliffsPrentice Hall/SunSoft Press, ISBN 0-13-443698-9)
http://www.sun.com/books/books/Lewis/Lewis.html
Programming Java threads in the real world, Part 3

Roll-your-own mutexes and centralized lock management

By Allen Holub, JavaWorld.com, 11/01/98

In last month's column, I demonstrated a simple deadlock scenario using two nested synchronized
blocks that acquired the same two locks, but in a different order. (Please review last month's
example if this isn't fresh in your mind.) This month, I'll take a look at a solution to this
commonplace deadlock problem, presenting a roll-your-own exclusion semaphore class and a
lock manager that supports the safe acquisition of multiple semaphores. Using these objects
rather than the built-in synchronized can save you hours of searching for unexpected deadlocks.
(They don't solve every possible deadlock problem, of course, but are nonetheless pretty
useful.)

When 'synchronized' isn't good enough

The nested-synchronized-statements example from last month was -- admittedly -- contrived, but
the multiple-lock situation comes up frequently in the real world. One common problem is the
too-coarse, object-level granularity of the synchronized keyword: there's only one monitor per
object, and sometimes that's not enough.

Consider the following class, the methods of which can be broken up into three partitions. The
methods in the first partition use only a subset of the fields in the class. The methods in the
second partition use a non-overlapping subset; they share fields that are not used by methods
in the first partition. The methods in the third partition use everything.

1| class Complicated    // NOT thread safe
2| {
3| private long a, b;
4| private long x, y;
5|
6| // partition 1, functions use a and/or b
7|
8| public void use_a()       { do_something_with(a); ); }
9| public void use_b()        { do_something_with(b); ); }
10 public void use_a_and_b() { do_something_with(a+b); ); }
11|
12| // partition 2, functions use x and/or y
13|
14| public void use_x()        { do_something_with(x); ); }
15| public void use_y()         { do_something_with(y); ); }
16| public void use_x_and_y() { do_something_with(x+y); ); }
17|
18| // partition 3, functions use a, b, x, and y
19|
20| public void use_everything() { do_something_with( a +
x ); }
21| public void use_everything_else(){ do_something_with( b +
y ); }
22| }



As it stands, this code is a multithreading disaster. Nothing is synchronized and we have
guaranteed race conditions. (A race condition occurs when two threads try to access the same
object at the same time, and chance determines which one wins the "race." Programs shouldn't
work by chance.) Synchronizing all the methods would fix the problem, but then you couldn't
call a method in partition 1 (emphasized in the code above) simply because some thread was
using a method from partition 2 above. Since these two partitions don't interact with each
other, this solution imposes needless access restrictions on the methods of the class. If you're
accessing any method in partition 3, though, you do want to lock out everything in the other
two partitions. We really need two locks in this situation. One to lock partition-1 variables and
another for partition-2 variables. The methods in partition 3 can then grab both locks.

Page 2 of 5

So, roll your own

You can implement the two-lock strategy by synchronizing on something other than the
containing object. Rather than synchronizing the methods, you can define two local variables to
use as locks and synchronize on them:

1| class Complicated_and_thread_safe
2| {
3| private long a, b;
4| private long x, y;
5|
6| private Object ab_lock = new int[];
7| private Object xy_lock = new int[];
8|
9| // partition 1, functions use a and/or b
10|
11| public void use_a() { synchronized(ab_lock){
 /*...*/ } }
12| public void use_b() { synchronized(ab_lock){
/*...*/ } }
13| public void use_a_and_b(){ synchronized(ab_lock){
/*...*/ } }
14|
15| // partition 2, functions use x and/or y
16|
17| public void use_x() { synchronized(xy_lock){
 /*...*/ } }
18| public void use_y() { synchronized(xy_lock){
/*...*/ } }
19| public void use_x_and_y(){ synchronized(xy_lock){
/*...*/ } }
20|
21| // partition 3, functions use a, b, x, and y
22|
23| public void use_everything()
24| { synchronized(ab_lock)        // grab both locks
25|        { synchronized(xy_lock)
26|          { /*...*/
27|          }
28|        }
29| }
30|
31| public void use_everything_else()
32| { synchronized(ab_lock)
33|        { synchronized(xy_lock)
34|          { /*...*/
35|          }
36|        }
37| }
38| }



I haven't synchronized the methods themselves in this example. (Remember, synchronizing a
method is effectively the same thing as wrapping all the code in the method in a
synchronized(this){...} block.) Instead, I'm providing a unique lock for each partition ( ab_lock and
xy_lock) and then explicitly synchronizing on these individual locks.

Java associates locks with objects (instance of some class that extends Object, so I can't use
primitive-type variables as locks here. I don't want to spend unnecessary time calling
constructors and doing other initialization operations on complicated objects, however.
Consequently, the locks themselves are declared as the simplest possible Object -- an array.

Arrays in Java are first-class objects: they implicitly extend the Object class. (If you don't believe
me, compile the following code:

1| public class foo
2| { static Object ref = new int[]{ 10 };
3|
4| public static void main( String[] args )
5| { System.out.println( ref.toString() );
6| }
7| }



The class compiles just fine. Not only does the implicit cast from the array to Object work
(because Object is a base class of all arrays), but the println() correctly invokes the compiler-
generated toString() override (which prints absolutely nothing useful -- but you can't have
everything). I've used a one-element array for my lock, rather than something like an Integer,
because arrays come into existence very efficiently. For example, there's no constructor call.

In the foregoing example, it's critical that methods that acquire both locks always acquire them
in the same order, otherwise we end up in the Wilma-and-Betty deadlock scenario discussed
last month. Acquiring multiple locks is a commonplace enough problem that some operating
systems have system calls for this purpose. It would be nice to have an easy way to acquire
multiple locks, in Java, without having to worry about the order-of-acquisition problem. The
remainder of this month's column describes one way to do that.

Page 3 of 5

Semaphores

Listing 1 shows the core interface I use for all my roll-your-own semaphore classes: the
Semaphore. It's in the com.holub.asynch package, as are all the thread-related classes and
interfaces I'll be discussing. (I've also put all the code that appears in the listings into the
"Goodies" section on my Web site; see Resources for the link.)

1| package com.holub.asynch;
2|
3| interface Semaphore
4| {
5| int id ();
6| void acquire(long timeout) throws InterruptedException;
7| void release();
8| }



Listing 1: Semaphore.java

If you haven't worked much with semaphores, a semaphore has to have two properties in order
to be useful:

    1. It must be able to identify itself using a unique ID when passed an id() request. The
       current implementation uses a unique integer for the purpose.

    2. You must be able to acquire and release the semaphore, though the semantic meaning
       of "acquire" and "release" can vary, depending on what sort of semaphore you're
       dealing with.



Managing the locks
Any semaphore that implements Semaphore can be locked in groups using the Lock_manager class
shown in Listing 2. There are several things going on here. First of all, I've used the Arrays.sort()
method, one of the new JDK 1.2 data-structure facilities, to make life easier. The Arrays class is a
"Booch utility" -- a class that contains nothing but static methods. The java.lang.Math utility is
another example. In fact, the Lock_manager itself is a utility, since it's made up solely of static
methods.

01| package com.holub.asynch;
02|
03| import com.holub.asynch.Semaphore;
04| import java.util.Arrays;
05| import java.util.Comparator;
06|
07| class Lock_manager
08| {
09| private static int[] id_lock = new int[1];
10| private static int id_pool = 0;
11|
12| public static int new_id( )
13| {
14|      int id;
15|      synchronized( id_lock ) {
16|        id = id_pool++; }
17|      return id;
18| }
19|
20| /**
21| * Every mutex is given a unique int ID when it's created.
22| * This function returns once all of the locks in the incoming
23| * array have been successfully acquired. Locks are always
24| * acquired in ascending order of ID to attempt to avoid
25| * deadlock situations.
26| *
27| * @param locks All of these locks must be acquired before
28| *         acquire_multiple returns.
29| * @param timeout Maximum time to wait to acquire each
30| *         lock (milliseconds). The total time for the multiple
31| *         acquire operation could be (timeout * locks.length).
32| **/
33|
34| public static void acquire_multiple( Semaphore[] locks, long timeout )
35|                                throws InterruptedException
36| { try
37|      {
38|        Arrays.sort(locks, new Lock_comparator() );
39|        for( int i = 0; i < locks.length; ++i )
40|            locks[i].acquire( timeout ) ;
41|      }
42|      catch( Comparable.Not_comparable e )
43|      {
44|        // shouldn't happen. Convert it to an uncaught
45|             // exception to terminate the program.
46|
47|             throw new Error( e.toString() );
48|         }
49|     }
50|
51|     private static class Lock_comparator implements Comparator
52|     { public int compare(Object a, Object b)
53|                 throws Comparable.Not_comparable
54|       { return ((Semaphore)a).id() - ((Semaphore)b).id();
55|       }
56|       public boolean equals(Object obj)
57|       { return obj instanceof Lock_comparator;
58|       }
59|     }
60| }



Listing 2: Lock_manager.java

"Booch utilities"
Digressing for a moment, in most object-oriented languages, utilities are kludges needed to get
around design problems in the language itself or in existing libraries. The problem in Java is that
neither arrays nor primitive types really are true objects. If they were, they would contain all
the methods needed to manipulate them. For example, you would be able to find a cosine
using:

int x = PI;
x.cosine();



You'd also be able to extend int, and so forth. In the case of arrays, you ought to be able to sort
them by asking them to sort themselves, like this:

int array[] = new int[];
//...
array.sort();



Unfortunately, Java isn't a "pure" object-oriented language in which everything is an object, so
we have to make do with a utility class.

You can use the Arrays utility as follows to sort an array of int:

int array[] = new int[];
//...
Arrays.sort( array );
The problem is complicated a bit by arrays of objects of some arbitrary class. How can a generic
sort utility figure out the correct ordering? The Object class contains an equals() function, but
we'd need a greater_than() as well to do sorting.

The Strategy pattern
To the rescue comes the "Strategy" pattern. The notion is to pass into a method or object
another object that encapsulates the strategy needed to do something. There are lots of
examples of Strategy in Java. A java.awt.LayoutManager, for example, encapsulates the strategy
that a Frame uses for laying itself out. You can change the layout simply by changing this
strategy. You don't have to define a new kind of Frame class using derivation or some other
mechanism. This, in turn, makes Frame much more "reusable" since the behavior of a Frame can
be changed without changing any of the code that comprises Frame.

The Arrays utility uses the Strategy pattern to define a comparison strategy. In Listing 2, the
Lock_comparator (at the bottom of the listing) implements the comparison strategy by
implementing the new (to JDK 1.2) java.util.Comparator interface and its compare() method. (This
works like C's strcmp(): it returns a negative number, zero, or a positive number depending on
whether a is less than, equal to, or greater than b.) An instance of the Comparator is passed to
the Arrays.sort() method like this:

Arrays.sort(locks, new Lock_comparator() );



The Lock_comparator object's compare() method is called by Arrays.sort() when it needs to compare
objects. C programmers will recognize this approach as very similar to that of qsort(), which is
passed a pointer to a compare method -- another example of Strategy.

How Lock_manager works
Now, let's look at what the Lock_manager actually does. It starts out with the static new_id()
method that returns a unique int every time it's called. This method will be called from the
various objects that implement the Semaphore interface to get the value they'll use for their ID.

The acquire multiple method is used to safely acquire groups of semaphores. You pass it an array
of objects that implement Semaphore, it sorts the array by ID, and then acquires the semaphores
one at a time in ID order. Consistent acquisition of multiple semaphores in ID order effectively
eliminates the deadlock scenario we've been discussing.

Page 4 of 5

A manageable mutex

Now let's move on and look at the other side of the equation: a class that implements a
semaphore. The Mutex class in Listing 3 implements a simple mutual-exclusion semaphore.
Whether it makes sense to use it (as compared to synchronizing on a lock object, as I did
earlier) really depends on the situation. The main advantage is in being able to use the
Lock_manager.acquire_multiple() to acquire several mutexes without worrying about an order-of-
acquisition deadlock. I personally think the code also looks a little cleaner. I prefer this:

1| class some_class
2| {
3| private Mutex lock = new Mutex();
4|
5| public void f()
6| { lock.acquire();
7|      //...
8|      lock.release();
9| }
10| }



to this:

1| class some_class
2| {
3| private Object lock = new int[1];
4|
5| public void f()
6| { synchronized( lock )
7|      {
8|        //...
9|      }
10| }
11| }



even though it's possible to forget the explicit release().

1| package com.holub.asynch;
2|
3| import com.holub.asynch.Semaphore;
4|
5| import com.holub.tools.Comparable;
6| import com.holub.tools.Sort;
7|
8| // Implementation of a mutual-exclusion semaphore. It can be owned by
9| // only one thread at a time. The thread can acquire it multiple times,
10| // but there must be a release for every acquire.
11|
12| public class Mutex implements Semaphore
13| {
14| private Thread owner = null; // Owner of mutex, null if nobody
15| private int lock_count = 0;
16|
17| private final int _id = Lock_manager.new_id();
18|   public      int id() { return _id;   }
19|
20|   /**
21|   * Acquire the mutex. The mutex can be acquired multiple times
22|   * by the same thread, provided that it is released as many
23|   * times as it is acquired. The calling thread blocks until
24|   * it has acquired the mutex. (There is no timeout).
25|   *
26|   * @see release
27|   * @see acquire_without_blocking
28|   */
29|
30|   public synchronized void acquire( long timeout )
31|                       throws InterruptedException
32|   { while( acquire_without_blocking() == false)
33|       this.wait( timeout );
34|   }
35|
36|   /**
37|   * Attempts to acquire the mutex. Returns false (and does not
38|   * block) if it can't get it.
39|   *
40|   * @see release
41|   * @see acquire
42|   */
43|
44|   public synchronized boolean acquire_without_blocking()
45|   {
46|     // Try to get the mutex. Return true if you got it.
47|
48|       if( owner == null )
49|       { owner = Thread.currentThread();
50|          lock_count = 1;
51|          return true;
52|       }
53|
54|       if( owner == Thread.currentThread() )
55|       { ++lock_count;
56|          return true;
57|       }
58|
59|       return false;
60|   }
61|
62|   /**
63|   * Release the mutex. The mutex has to be released as many times
64|   * as it was acquired to actually unlock the resource. The mutex
65|   * must be released by the thread that acquired it
66|   *
67|   * @throws Mutex.OwnershipException (a RuntimeException) if a thread
68|   * other than the current owner tries to release the mutex.
69|   */
70|
71|     public synchronized void release()
72|     {
73|       if( owner != Thread.currentThread() )
74|          throw new OwnershipException();
75|
76|         if( --lock_count <= 0 )
77|         { owner = null;
78|            notify();
79|         }
80|     }
81|
82|     public static class OwnershipException extends RuntimeException
83|     { public OwnershipException()
84|       { super("Thread calling release() doesn't own Mutex");
85|       }
86|     }
87| }



Listing 3: Mutex.java

Admittedly, this preference for the explicit call to acquire() is largely a matter of personal taste.
We'll look at a few other semaphore implementations next month that are a bit harder to
simulate with a simple synchronized, however.

Getting back to Listing 3, the Mutex class starts out with the stuff needed to keep the
Lock_manager happy. It implements the Semaphore interface with an id() method that returns the
value of the _id field, which in turn holds a unique value that came from the Lock_manager.

There are two versions of the acquire method: acquire() itself and acquire_without_blocking(). The
latter simply returns false if it can't acquire the mutex. If the mutex isn't owned, it sets owner to
reference the thread that called acquire_without_blocking() with the call to Thread.currentThread(),
which does the obvious. The blocking version, acquire(), calls the nonblocking version and
suspends itself with a wait() call if it can't get the mutex right off the bat. Note the use of a spin
lock here. (I discussed spin locks last month.) The wait() call is inside a loop in case some other
thread breaks in at an inopportune moment and steals the mutex.

Interestingly, the Mutex code doesn't actually use the Mutex object's monitor to implement the
lock (though it does use the monitor to make sure that two threads don't try to acquire the
same mutex simultaneously -- acquire() and release() are synchronized). A local field called owner is
used to decide whether or not to block out an acquiring thread. The owner field references the
Thread instance that contains the run() method for a given thread. If owner is null, the mutex is up
for grabs, otherwise, some thread "owns" the mutex and any other thread that tries to acquire
it will block at the wait() call in acquire().
My Mutex class implements a "recursive" mutex. The "owner" thread can acquire the mutex
more than once, but it must release the mutex by calling release() as many times as it acquired it
by calling acquire().

This facility is essential when two methods both must acquire the mutex. In the following code,
for example, g() calls f(), but f() can also be called from outside -- without going through g() first.
If the mutex weren't recursive, the thread that called g() would block when g() called f() and tried
to acquire the mutex a second time. As it is, the double acquisition isn't a problem since every
acquire() has a corresponding release(). The lock_count field keeps track of the number of times the
mutex has been locked by its owner.

1| class some_class
2| { Mutex lock = new Mutex();
3|
4| public void f()
5| { lock.acquire();
6|      //...
7|      lock.release();
8| }
9|
10| public void g()
11| { lock.acquire();
12|       //...
13|       f();
14|       //...
15|       lock.release();
16| }
17| }



Until next time

So that's a simple roll-your-own semaphore. Though it's easy enough to use Java's synchronized
statement directly to do everything the Mutex does, the code is cleaner when you use the Mutex
class, and the associated Lock_manager can solve a class of otherwise hard-to-manage deadlock
problems.

Page 5 of 5

Next month I'll look at a couple of other useful semaphores: a condition variable and a Djikstra
counting semaphore. I'll also discuss critical sections and class-level locks. In future columns,
we'll take a look at other thread-related classes, such as timers and reader/writer locks, and
explore architectural solutions to threading-related problems.

About the author
Allen Holub has been working in the computer industry since 1979. He is widely published in
magazines (Dr. Dobb's Journal, Programmers Journal, Byte, MSJ, among others). He has seven
books to his credit, and is currently working on an eighth that will present the complete sources
for a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now looks at
C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming
(first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University of
California Berkeley Extension since 1982. Allen offers both public classes and in-house training
in Java and object-oriented design topics. He also does object-oriented design consulting. Get
more information and contact Allen via his Web site http://www.holub.com.

Resources


       All the real code (the stuff in the com.holub.asynch package) is available in the
       "Goodies" section on my Web site http://www.holub.com
Programming Java threads in the real world, Part 4

Condition variables and counting semaphores -- filling in a few chinks in Java's
threading model

By Allen Holub, JavaWorld.com, 12/01/98

This month's column adds a few more classes to the threading arsenal we started building in
last month's Java Toolbox column. This time I'll discuss the following:

   1. Roll-your-own versions of the condition variable, which replaces wait() and notify() in
      some situations

   2. Djikstra's "counting" semaphore, which is used to manage pools of resources



(A semaphore is any of several mechanisms used to synchronize and communicate between
threads. Think of the "semaphore" as in the flags that two boy scouts use to talk to each other
from a distance -- different flag positions represent different letters of the alphabet. Napoleon's
army used the vanes of windmills on mountain tops to send semaphore messages great
distances very quickly. The mutex discussed last month, since it's a communications
mechanism, is also a "semaphore.")

The condition variable can often be simulated using Java alone -- and I'll show you how -- while
the counting semaphore can't.

Before I start, though, I'd like to go over a few loose ends from last month and fix a few bugs.

Oops! Could my code have bugs in it?

Before leaping into this month's meat (now there's a metaphor you can sink your teeth into),
let's look at a few loose ends that either caught my eye (or were brought to my attention) after
last month's article went live.

During one of my book's "peer reviews," an academic reviewer once took exception to the
sentence "if you're anything like me, you'll forget to ... so you should write your code to do it
automatically." His comment to me was: "I would never admit that in print." This guy was (and
as far as I know, still is) a tenured professor at an Ivy League university, and I suppose his
comment was correct in a literal sense: since he never had written any actual code, he never
had any bugs to admit. I might as well say it up front, though: my code contains an occasional
bug (gasp). Consequently, I expect an "Oops" section or its moral equivalent to become a
regular feature of this column. There's nothing like having 100,000 people look over your code
for problems to emerge and be highlighted.
Joe Bowbeer pointed out (quite correctly):

Why not advise the use of try/finally to prevent an exception from gunking up the works?

mutex.acquire();
try
{ doit();
}
finally
{ mutex.release();
}



I prefer the [above] form of try/finally because it separates the exceptions that might occur in
changing the state (acquire) from the exceptions that might occur when working in the new state
(doit).

The other (more intuitive?) form is

try
{ mutex.acquire();
   doit();
}
finally
{ mutex.release();
}



This requires more programming in release() to ensure that mutex is in a consistent state: if
release() is called after an exception in acquire(), the mutex may not have been acquired, or [may
have been] half-acquired, etc.



I should add that Joe's last point is important in the case of last month's Mutex class. The
acquire_without_blocking() method, where the actual acquisition occurs, doesn't throw any
exceptions at awkward times. The only exception that can be thrown from acquire(), in fact, is an
InterruptedException, thrown if a timeout is specified and the waiting thread is interrupted. This
operation does not leave the mutex in an unstable state, however.

Page 2 of 7

Be that as it may, while looking at my code to make sure Joe hadn't found a bug, I found a bug
myself. (I'll show you the code in a moment, but let's discuss the problems in it first.) The
acquire() method was using a standard spin lock while waiting to acquire the mutex, but this
strategy is appropriate only when the timeout is infinitely large. I've fixed the problem by
making the loop terminate for timeout values less than Integer.MAX_VALUE (the value I use for
"forever"). It continues to use a spin lock in "forever" cases.

While I was at it, I also decided to have acquire() indicate whether or not it had timed out. The
choices here are the usual ones: a return value or an exception toss. I opted for the latter
because a timeout typically is an error condition, and I didn't want to clutter up the code with
unnecessary tests for false return values.

I modified the definition for the Semaphore interface to incorporate this new exception:

01 | package com.holub.asynch;
02 |
03 | interface Semaphore
04 | {
05 | int id ();
06 | void acquire(long timeout) throws InterruptedException,
07 |                       Timed_out;
08 | void release();
09 |
10 | public static class Timed_out extends java.lang.RuntimeException
11 | { Timed_out(){ super("Timed out while waiting to acquire semaphore"); };
12 | }
13 | }



Note that Semaphore.Timed_out is a RuntimeException, so you don't have to catch it if the timeout is
a fatal error (often the case).

The new (and this time, I hope, correct) version of acquire() now looks like this:

01 | public synchronized void acquire( long timeout ) throws InterruptedException
02 | {
03 | if( timeout == 0 )               // don't wait at all
04 | { acquire_without_blocking();
05 | }
06 | else if( timeout == Long.MAX_VALUE ) // wait forever
07 | { while( !acquire_without_blocking() )
08 |        this.wait( timeout );
09 | }
10 | else                        // wait limited by timeout
11 | { if( !acquire_without_blocking() )
12 |     { this.wait( timeout );
13 |        if( !acquire_without_blocking() )
14 |           throw new Semaphore.Timed_out();
15 |     }
16 | }
17 | }
Finally, in last month's column, I inadvertently used an outdated version of the Lock_manager's
comparator class. (It threw a Not_Comparable exception -- an artifact of my own sort
implementation, which I abandoned when Java added an official one.) Anyway, the comparator
class should look like this:

private static class Lock_comparator implements Comparator
{ public int compare(Object a, Object b)
  { return ((Semaphore)a).id() - ((Semaphore)b).id();
  }
  public boolean equals(Object obj)
  { return obj instanceof Lock_comparator;
  }
}



I've modified the code in the "Goodies" section of my Web site (see Resources) to incorporate
all these changes.

Now, on to the meat of this month's article.

Condition variables

Forging ahead -- to boldly split infinitives no one has split before:

I've brought up "condition variables" before in the context of wait() and notify(). The central
concept is that a thread will wait until some condition becomes true. For example, a thread
may need to wait for somebody to push a button before proceeding with an action, or a thread
may wait for something to appear in an empty queue (for the queue-not-empty condition to
become true).

Page 3 of 7

Using a condition variable to wait for events
The following code illustrates a classic problem that is easily solved with a condition variable:
How do I wait for an event to occur without wasting machine cycles in a polling loop? The code
sets up a simple TextField and an ActionListener that's notified when the user types the Enter key:

01 | class Some_class extends Frame
02 | {
03 | TextField input = new TextField();
04 | String entered = "";
05 |
06 | public The_wrong_way()
07 | { input.addActionListener
08 |      ( new ActionListener()
09 |        { public void actionPerformed( ActionEvent e )
10 |          { entered = input.getText();
11 |         }
12 |       }
13 |   );
14 |
15 |    add(input);
16 |   pack();
17 |    show();
18 | }
19 |
20 | String read_line(){ return entered; }
21 | //...
22 | }



So far, so good, but let's look at the situation in more detail.

When you display the Frame, AWT fires up a thread that monitors events coming in from the
operating system, including key-press events. When the Enter key is pressed, for example, the
AWT thread gets the key-press event from the OS and, in turn, calls the listener's
actionPerformed() method. The "actionPerformed()" messages are coming in asynchronously from
the AWT event-loop thread. Put another way: the "actionPerformed()" message is actually
running on that AWT thread.

Meanwhile, a user thread (as differentiated from the AWT thread) calls read_line() to find out
what the user has typed. The problem is that both the AWT and the user thread can access the
entered field simultaneously -- a classic race condition. The second thread could call read_line()
while the AWT thread is in the middle of ActionPerformed() and end up reading garbage.

Solve this first problem with synchronization:

01 | class Some_class extends Frame
02 | {
03 | TextField input = new TextField();
04 | String entered = "";
05 |
06 | public The_wrong_way()
07 | { input.addActionListener
08 |      ( new ActionListener()
09 |         { public void actionPerformed( ActionEvent e )
10 |           { synchronized( Some_class.this )
11 |             { entered = input.getText();
12 |             }
13 |           }
14 |         }
15 |      );
16 |
17 |      add(input);
18 |      pack();
19 |      show();
20 | }
21 |
22 | String synchronized read_line(){ return entered; }
23 | //...
24 | }



Note that the inner-class method has to synchronize explicitly on the outer-class object. Simply
synchronizing actionPerformed() doesn't work because you'll be synchronizing on the monitor of
the anonymous inner-class object, and the field you want to guard is in the outer-class object.

Moving on, our user thread needs to know when an entire line has been typed to be sure that
read_line() will return a complete line of input, but (and this is the big but), there's no direct
communication between the two threads involved in this transaction. The code running on the
AWT thread (actionPerformed()) doesn't tell the user thread that an entire-line-has-been-typed
event has occurred.

So how does the caller of read_line() know the string has changed? It could sit in a tight polling
loop calling read_line() and checking the current return value against the previously returned
value, but that's an awful lot of machine cycles wasted on doing nothing useful.

Page 4 of 7

Send in the cavalry
So what's one way for two threads to communicate with each other? (That's a rhetorical
question.) Use a semaphore (think Napoleon, flags, mountain tops). To the rescue comes the
semaphore known as a condition variable. To rehash from previous months' material: the basic
notion of a condition variable is that some thread waits (is suspended) on the condition variable
until the condition it represents becomes true. Every Java object has a condition variable
associated with it -- in the same way it has the mutex used to guard the monitor. You wait for
the condition to become true by calling wait(), and you set the condition to true by calling
notify(). (The notify() call doesn't work in quite this way; I'll talk more about this in a moment.) It's
easy enough to do a roll-your-own condition variable that solves the current thread-
communication problem by using wait() and notify().

Listing 1 demonstrates how to do this. A condition variable called text_has_been_entered is
declared up at the top of the class definition. (We're going to wait for the text-has-been-
entered condition to become true.) The actionPerformed() method doesn't read the text at all;
rather, it simply notifies the condition variable, setting the condition to true. Note that Java
requires you to be in the monitor for an object before you can call wait() or notify() on that
object, so the synchronized(text_has_been_entered) statement is mandatory. (You must be holding
the lock associated with the object, by being in either a synchronized function of that object's
class or a standalone synchronized statement whose argument is the object on which you're
synchronizing.)
The synchronized(text_has_been_entered) statement on line 15 is mandatory, since entering the
synchronized block puts us into the monitor of the object referenced by text_has_been_entered.

Meanwhile, down in read_line() on line 33, the thread that calls read_line() is waiting for the
condition to become true. When this happens, the new text value is read and returned. The
read_line() method is itself synchronized so that two threads can't attempt to read the same line
simultaneously. It's now possible to have a simple loop like the one in main()

while( (input = source.read_line()) != null )
System.out.println("Got: " + input );



which blocks until a new input line arrives.


       Listing 1: Using wait() and notify() for a condition variable



01 | import java.awt.*;
02 | import java.awt.event.*;
03 |
04 | public class Input_source extends Frame
05 | {
06 | int[] text_has_been_entered = new int[1]; // The condition variable
07 |
08 | TextField input = new TextField();
09 |
10 | public Input_source()
11 | {
12 |     input.addActionListener
13 |     ( new ActionListener()
14 |        { public void actionPerformed( ActionEvent e )
15 |           { synchronized( text_has_been_entered )
16 |             { text_has_been_entered.notify(); // set the condition true
17 |             }
18 |           }
19 |        }
20 |     );
21 |
22 |     add(input);
23 |     pack();
24 |     show();
25 | }
26 |
27 | /** A blocking function that works like readLine(), but gets its
28 | * text from the current window's text area. The function doesn't
29 | * return until somebody types a line of text, whereupon it returns
30 | * the line. Returns null if the user types an empty line.
31 |     */
32 |
33 |     synchronized String read_line() throws InterruptedException
34 |     { synchronized( text_has_been_entered )
35 |       { text_has_been_entered.wait(); // wait for the condition to become true
36 |       }
37 |
38 |         String entered = input.getText();
39 |         input.setText("");
40 |         return (entered.length() == 0) ? null : entered;
41 |
42 |     }
43 |
44 |
45 |     static public void main( String[] args ) throws Exception
46 |     { Input_source source = new Input_source();
47 |       String input;
48 |
49 |         while( (input = source.read_line()) != null )
50 |          System.out.println("Got: " + input );
51 |
52 |         System.exit(0);      // kill the AWT Thread on exit
53 |     }
54 | }




But Wellington is coming
A subtle problem occurs when using Java's built-in condition variable, however. What if you call
read_line() just after the value has changed rather than before? You'll just wait until the value is
changed again, missing the first value entirely. The problem is that the built-in condition
variable doesn't really have a notion of state associated with it. What we really need, to solve
the current problem, is a true condition variable -- one that blocks only if we try to wait() when
the condition is false, and that doesn't block at all if the condition is true.

Page 5 of 7

Listing 2 shows a simple implementation of such a beast. There's not much to it -- the vast
majority of the file is comments. The main thing is the _is_true flag declared at the top of the
class. The _is_true flag stores the state of the condition variable. You can set the condition to
true or false by calling set_true() or set_false(). You can test the current state without blocking by
calling is_true() or is_false(). You can block, waiting for the condition to become true by calling
wait_for_true(), which doesn't block at all if the condition happens to be true when you call it.


                 Listing 2: A condition-variable implementation

001 | package com.holub.asynch;
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub
17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub

Contenu connexe

Tendances

Kioti Daedong CK20 Tractor Service Repair Manual
Kioti Daedong CK20 Tractor Service Repair ManualKioti Daedong CK20 Tractor Service Repair Manual
Kioti Daedong CK20 Tractor Service Repair Manual
hjnnsemmm
 
Engineering chemistry
Engineering chemistryEngineering chemistry
Engineering chemistry
student
 
INVESTIGATION INTO THE AERODYNAMIC DESIGN OF A FORMULA ONE CAR
INVESTIGATION INTO THE AERODYNAMIC DESIGN OF A FORMULA ONE CARINVESTIGATION INTO THE AERODYNAMIC DESIGN OF A FORMULA ONE CAR
INVESTIGATION INTO THE AERODYNAMIC DESIGN OF A FORMULA ONE CAR
Daniel Baker
 

Tendances (15)

Domain Study Indian Power Sector
Domain Study Indian Power SectorDomain Study Indian Power Sector
Domain Study Indian Power Sector
 
Kioti daedong ck20 tractor service repair manual
Kioti daedong ck20 tractor service repair manualKioti daedong ck20 tractor service repair manual
Kioti daedong ck20 tractor service repair manual
 
Kioti daedong ck20 h tractor service repair manual
Kioti daedong ck20 h tractor service repair manualKioti daedong ck20 h tractor service repair manual
Kioti daedong ck20 h tractor service repair manual
 
Kioti daedong ck20 hj tractor service repair manual
Kioti daedong ck20 hj tractor service repair manualKioti daedong ck20 hj tractor service repair manual
Kioti daedong ck20 hj tractor service repair manual
 
Kioti daedong ck20 j tractor service repair manual
Kioti daedong ck20 j tractor service repair manualKioti daedong ck20 j tractor service repair manual
Kioti daedong ck20 j tractor service repair manual
 
Kioti daedong ch20 tractor service repair manual
Kioti daedong ch20 tractor service repair manualKioti daedong ch20 tractor service repair manual
Kioti daedong ch20 tractor service repair manual
 
Kioti daedong ck20 h tractor service repair manual
Kioti daedong ck20 h tractor service repair manualKioti daedong ck20 h tractor service repair manual
Kioti daedong ck20 h tractor service repair manual
 
Kioti Daedong CK20 Tractor Service Repair Manual
Kioti Daedong CK20 Tractor Service Repair ManualKioti Daedong CK20 Tractor Service Repair Manual
Kioti Daedong CK20 Tractor Service Repair Manual
 
The Best Spinner for re write content
The Best Spinner for re write content The Best Spinner for re write content
The Best Spinner for re write content
 
AERODYNAMIC DEVELOPMENT OF THE SEGURACING F1-R01 PROTOTYPE USING CFD
AERODYNAMIC DEVELOPMENT OF THE SEGURACING F1-R01 PROTOTYPE USING CFDAERODYNAMIC DEVELOPMENT OF THE SEGURACING F1-R01 PROTOTYPE USING CFD
AERODYNAMIC DEVELOPMENT OF THE SEGURACING F1-R01 PROTOTYPE USING CFD
 
Orbiter 2010 manual.
Orbiter 2010 manual.Orbiter 2010 manual.
Orbiter 2010 manual.
 
Tu dien tranh tau thuy
Tu dien tranh tau thuyTu dien tranh tau thuy
Tu dien tranh tau thuy
 
98432535 women-empowerment-project
98432535 women-empowerment-project98432535 women-empowerment-project
98432535 women-empowerment-project
 
Engineering chemistry
Engineering chemistryEngineering chemistry
Engineering chemistry
 
INVESTIGATION INTO THE AERODYNAMIC DESIGN OF A FORMULA ONE CAR
INVESTIGATION INTO THE AERODYNAMIC DESIGN OF A FORMULA ONE CARINVESTIGATION INTO THE AERODYNAMIC DESIGN OF A FORMULA ONE CAR
INVESTIGATION INTO THE AERODYNAMIC DESIGN OF A FORMULA ONE CAR
 

Similaire à 17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub

Arduino: Crea bots y gadgets Arduino aprendiendo mediante el descubrimiento d...
Arduino: Crea bots y gadgets Arduino aprendiendo mediante el descubrimiento d...Arduino: Crea bots y gadgets Arduino aprendiendo mediante el descubrimiento d...
Arduino: Crea bots y gadgets Arduino aprendiendo mediante el descubrimiento d...
SANTIAGO PABLO ALBERTO
 
Spm6600 Dx6i Manual Lo Res
Spm6600 Dx6i Manual Lo ResSpm6600 Dx6i Manual Lo Res
Spm6600 Dx6i Manual Lo Res
guestada3c8
 
Arduino: Realice proyectos básicos de Arduino 26 experimentos con microcontro...
Arduino: Realice proyectos básicos de Arduino 26 experimentos con microcontro...Arduino: Realice proyectos básicos de Arduino 26 experimentos con microcontro...
Arduino: Realice proyectos básicos de Arduino 26 experimentos con microcontro...
SANTIAGO PABLO ALBERTO
 

Similaire à 17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub (20)

Press Kit for Space Shuttle Endeavour's Final Mission, STS-134
Press Kit for Space Shuttle Endeavour's Final Mission, STS-134Press Kit for Space Shuttle Endeavour's Final Mission, STS-134
Press Kit for Space Shuttle Endeavour's Final Mission, STS-134
 
Arduino: Crea bots y gadgets Arduino aprendiendo mediante el descubrimiento d...
Arduino: Crea bots y gadgets Arduino aprendiendo mediante el descubrimiento d...Arduino: Crea bots y gadgets Arduino aprendiendo mediante el descubrimiento d...
Arduino: Crea bots y gadgets Arduino aprendiendo mediante el descubrimiento d...
 
Tutorial sketch up_bonnie_roske
Tutorial sketch up_bonnie_roskeTutorial sketch up_bonnie_roske
Tutorial sketch up_bonnie_roske
 
A Real Time Application Integration Solution
A Real Time Application Integration SolutionA Real Time Application Integration Solution
A Real Time Application Integration Solution
 
Home Security Systems Reno | DSC powerseries users guide
Home Security Systems Reno | DSC powerseries users guideHome Security Systems Reno | DSC powerseries users guide
Home Security Systems Reno | DSC powerseries users guide
 
Spam 10
Spam 10Spam 10
Spam 10
 
Commonwealth Life Perusahaan Asuransi Jiwa Terbaik Indonesia
Commonwealth Life Perusahaan Asuransi Jiwa Terbaik IndonesiaCommonwealth Life Perusahaan Asuransi Jiwa Terbaik Indonesia
Commonwealth Life Perusahaan Asuransi Jiwa Terbaik Indonesia
 
G2010ser_OnlineManual_Win_EN_V02.pdf
G2010ser_OnlineManual_Win_EN_V02.pdfG2010ser_OnlineManual_Win_EN_V02.pdf
G2010ser_OnlineManual_Win_EN_V02.pdf
 
An introduction-to-tkinter
An introduction-to-tkinterAn introduction-to-tkinter
An introduction-to-tkinter
 
Libro joomla 2.5
Libro joomla 2.5Libro joomla 2.5
Libro joomla 2.5
 
Mazda 2013-2014 Navigation Owners Manual
Mazda 2013-2014 Navigation Owners ManualMazda 2013-2014 Navigation Owners Manual
Mazda 2013-2014 Navigation Owners Manual
 
Spm6600 Dx6i Manual Lo Res
Spm6600 Dx6i Manual Lo ResSpm6600 Dx6i Manual Lo Res
Spm6600 Dx6i Manual Lo Res
 
Arduino: Realice proyectos básicos de Arduino 26 experimentos con microcontro...
Arduino: Realice proyectos básicos de Arduino 26 experimentos con microcontro...Arduino: Realice proyectos básicos de Arduino 26 experimentos con microcontro...
Arduino: Realice proyectos básicos de Arduino 26 experimentos con microcontro...
 
Micrologic 5 6_2
Micrologic 5 6_2Micrologic 5 6_2
Micrologic 5 6_2
 
Manual
ManualManual
Manual
 
Windows XP Registry Guide
Windows XP Registry GuideWindows XP Registry Guide
Windows XP Registry Guide
 
Ingersoll rand zx75 load excavator service repair manual
Ingersoll rand zx75 load excavator service repair manualIngersoll rand zx75 load excavator service repair manual
Ingersoll rand zx75 load excavator service repair manual
 
Ingersoll rand zx75 load excavator service repair manual
Ingersoll rand zx75 load excavator service repair manualIngersoll rand zx75 load excavator service repair manual
Ingersoll rand zx75 load excavator service repair manual
 
Ingersoll rand zx125 load excavator service repair manual
Ingersoll rand zx125 load excavator service repair manualIngersoll rand zx125 load excavator service repair manual
Ingersoll rand zx125 load excavator service repair manual
 
Ingersoll rand zx125 load excavator service repair manual
Ingersoll rand zx125 load excavator service repair manualIngersoll rand zx125 load excavator service repair manual
Ingersoll rand zx125 load excavator service repair manual
 

Plus de Márcio Antônio Moraes Reyes

Plus de Márcio Antônio Moraes Reyes (20)

Criptografia_Métodos_E_Tecnicas_Criptograficas.ppt
Criptografia_Métodos_E_Tecnicas_Criptograficas.pptCriptografia_Métodos_E_Tecnicas_Criptograficas.ppt
Criptografia_Métodos_E_Tecnicas_Criptograficas.ppt
 
Introdução
IntroduçãoIntrodução
Introdução
 
Instalação elétrica
Instalação elétricaInstalação elétrica
Instalação elétrica
 
Trabalho tribal01.svg
Trabalho tribal01.svgTrabalho tribal01.svg
Trabalho tribal01.svg
 
3dxp_programming
3dxp_programming3dxp_programming
3dxp_programming
 
67580196-receitas
67580196-receitas67580196-receitas
67580196-receitas
 
4490170-Arabic-Cooking-Recipes
4490170-Arabic-Cooking-Recipes4490170-Arabic-Cooking-Recipes
4490170-Arabic-Cooking-Recipes
 
Fluxo Menus Editáveis
Fluxo Menus EditáveisFluxo Menus Editáveis
Fluxo Menus Editáveis
 
acordes y escalas para guitarra
acordes y escalas para guitarraacordes y escalas para guitarra
acordes y escalas para guitarra
 
Joan_Baez-Diamonds_And_Rust
Joan_Baez-Diamonds_And_RustJoan_Baez-Diamonds_And_Rust
Joan_Baez-Diamonds_And_Rust
 
3106741-MySQLStoredProcedures
3106741-MySQLStoredProcedures3106741-MySQLStoredProcedures
3106741-MySQLStoredProcedures
 
ART169_tut_flash
ART169_tut_flashART169_tut_flash
ART169_tut_flash
 
ART146_tut_4
ART146_tut_4ART146_tut_4
ART146_tut_4
 
FINNISH GRAMMAR
FINNISH GRAMMARFINNISH GRAMMAR
FINNISH GRAMMAR
 
br-ficha-inscripcion-beca-odp
br-ficha-inscripcion-beca-odpbr-ficha-inscripcion-beca-odp
br-ficha-inscripcion-beca-odp
 
Documento sem título
Documento sem títuloDocumento sem título
Documento sem título
 
19321927-MySQL-and-Java
19321927-MySQL-and-Java19321927-MySQL-and-Java
19321927-MySQL-and-Java
 
4488990-IndianRecipes
4488990-IndianRecipes4488990-IndianRecipes
4488990-IndianRecipes
 
Escalas de Blues GUITARRA
Escalas de Blues GUITARRAEscalas de Blues GUITARRA
Escalas de Blues GUITARRA
 
Finnish Conversation
Finnish ConversationFinnish Conversation
Finnish Conversation
 

Dernier

Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 

Dernier (20)

Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...
Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...
Apidays Singapore 2024 - Scalable LLM APIs for AI and Generative AI Applicati...
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
A Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source MilvusA Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source Milvus
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu SubbuApidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 

17419218-Programming-Java-Threads-in-the-Real-World-Allen-Hollub

  • 1. Java World Articles (www.javaworld.com) Programming Java Threads In The Real World Allen Hollub 98
  • 2. Contents Programming Java threads in the real world, Part 1 ....................................................................4 Platform dependence...........................................................................................................5 Atomic energy ......................................................................................................................5 Concurrency versus parallelism ............................................................................................7 Get your priorities straight ...................................................................................................8 Cooperate! .........................................................................................................................10 Mapping kernel threads to user processes .........................................................................11 Wrapping it up ...................................................................................................................13 About the author ...............................................................................................................13 Programming Java threads in the real world, Part 2 ..................................................................15 The perils of race conditions, deadlock, and other threading problems ..............................15 Monitors, mutexes, and bathrooms ...................................................................................15 Why is stop() deprecated in JDK 1.2? .................................................................................16 Race conditions and spin locks ...........................................................................................17 Working with wait() and notify() ........................................................................................17 Threads are not objects......................................................................................................20 The deadlock scenario ........................................................................................................22 Get out the magnifying glass ..............................................................................................23 Nested-monitor lockout .....................................................................................................25 Conclusion .........................................................................................................................26 About the author ...............................................................................................................27 Resources ..........................................................................................................................27 Programming Java threads in the real world, Part 3 ..................................................................29 Roll-your-own mutexes and centralized lock management ................................................29 When 'synchronized' isn't good enough .............................................................................29 So, roll your own ................................................................................................................30 Semaphores .......................................................................................................................32 Managing the locks ............................................................................................................32 A manageable mutex .........................................................................................................35
  • 3. Until next time ...................................................................................................................39 About the author ...............................................................................................................39 Programming Java threads in the real world, Part 4 ..................................................................41 Condition variables and counting semaphores -- filling in a few chinks in Java's threading model ................................................................................................................................41 Oops! Could my code have bugs in it? ................................................................................41 Condition variables ............................................................................................................44 Counting semaphores ........................................................................................................52 Wrapping up ......................................................................................................................56 About the author ...............................................................................................................57 Programming Java threads in the real world, Part 5 ..................................................................58 Has Sun abandoned 'run anywhere'? Plus: Threads and Swing, timers, and getting around stop(), suspend(), and resume() deprecation ......................................................................58 Whatever happened to 'run anywhere'? ............................................................................58 Back to threads ..................................................................................................................59 Swingin' threads .................................................................................................................60 Using the Swing Timer........................................................................................................62 Roll your own .....................................................................................................................70 How does an Alarm work? .................................................................................................71 Happy trails ........................................................................................................................88 Back to threads ..................................................................................................................89 Swingin' threads .................................................................................................................90 Using the Swing Timer........................................................................................................92 Roll your own ................................................................................................................... 100 How does an Alarm work? ............................................................................................... 102 Happy trails ...................................................................................................................... 118 Programming Java threads in the real world, Part 6 ................................................................ 120 The Observer pattern and mysteries of the AWTEventMulticaster ................................... 120 Implementing Observer in a multithreaded world ............................................................ 120 Observer-side problems: inner-class synchronization ....................................................... 121
  • 4. Notifier-side problems: notifications in a multithreaded world ........................................ 124 Mysteries of the AWTEventMulticaster ............................................................................ 133 Wrapping up .................................................................................................................... 141 Programming Java threads in the real world, Part 7 ................................................................ 143 Singletons, critical sections, and reader/writer locks ........................................................ 143 Critical sections, singletons, and the Class object ............................................................. 143 Singletons ........................................................................................................................ 146 Reader/writer locks .......................................................................................................... 154 It's a wrap ........................................................................................................................ 164 About the author ............................................................................................................. 164 Programming Java threads in the real world, Part 8 ................................................................ 166 Threads in an object-oriented world, thread pools, implementing socket 'accept' loops .. 166 Synchronous vs. asynchronous messages ......................................................................... 166 The thread-per-method solution ...................................................................................... 167 Thread pools and blocking queues ................................................................................... 172 Sockets and thread pools ................................................................................................. 183 Conclusion ....................................................................................................................... 193 About the author ............................................................................................................. 193 Programming Java threads in the real world, Part 9 ................................................................ 195 More threads in an object-oriented world: Synchronous dispatchers, active objects, detangling console I/O ..................................................................................................... 195 Synchronous dispatching ................................................................................................. 195 Active objects................................................................................................................... 205 Wrapping things up, books, and JavaOne ......................................................................... 219 And now for something completely different... ................................................................ 219 Programming Java threads in the real world, Part 1 By Allen Holub, JavaWorld.com, 10/01/98
  • 5. All Java programs other than simple console-based applications are multithreaded, whether you like it or not. The problem is that the Abstract Windowing Toolkit (AWT) processes operating system (OS) events on its own thread, so your listener methods actually run on the AWT thread. These same listener methods typically access objects that are also accessed from the main thread. It may be tempting, at this point, to bury your head in the sand and pretend you don't have to worry about threading issues, but you can't usually get away with it. And, unfortunately, virtually none of the books on Java addresses threading issues in sufficient depth. (For a list of helpful books on the topic, see Resources.) This article is the first in a series that will present real-world solutions to the problems of programming Java in a multithreaded environment. It's geared to Java programmers who understand the language-level stuff (the synchronized keyword and the various facilities of the Thread class), but want to learn how to use these language features effectively. Platform dependence Unfortunately, Java's promise of platform independence falls flat on its face in the threads arena. Though it's possible to write a platform-independent multithreaded Java program, you have to do it with your eyes open. This isn't really Java's fault; it's almost impossible to write a truly platform-independent threading system. (Doug Schmidt's ACE [Adaptive Communication Environment] framework is a good, though complex, attempt. See Resources for a link to his program.) So, before I can talk about hard-core Java-programming issues in subsequent installments, I have to discuss the difficulties introduced by the platforms on which the Java virtual machine (JVM) might run. Atomic energy The first OS-level concept that's important to understand is atomicity. An atomic operation cannot be interrupted by another thread. Java does define at least a few atomic operations. In particular, assignment to variables of any type except long or double is atomic. You don't have to worry about a thread preempting a method in the middle of the assignment. In practice, this means that you never have to synchronize a method that does nothing but return the value of (or assign a value to) a boolean or int instance variable. Similarly, a method that did a lot of computation using only local variables and arguments, and which assigned the results of that computation to an instance variable as the last thing it did, would not have to be synchronized. For example: class some_class { int some_field; void f( some_class arg ) // deliberately not synchronized { // Do lots of stuff here that uses local variables
  • 6. // and method arguments, but does not access // any fields of the class (or call any methods // that access any fields of the class). // ... some_field = new_value; // do this last. } } On the other hand, when executing x=++y or x+=y, you could be preempted after the increment but before the assignment. To get atomicity in this situation, you'll need to use the keyword synchronized. All this is important because the overhead of synchronization can be nontrivial, and can vary from OS to OS. The following program demonstrates the problem. Each loop repetitively calls a method that performs the same operations, but one of the methods (locking()) is synchronized and the other (not_locking()) isn't. Using the JDK "performance-pack" VM running under Windows NT 4, the program reports a 1.2-second difference in runtime between the two loops, or about 1.2 microseconds per call. This difference may not seem like much, but it represent a 7.25- percent increase in calling time. Of course, the percentage increase falls off as the method does more work, but a significant number of methods -- in my programs, at least -- are only a few lines of code. import java.util.*; class synch { synchronized int locking (int a, int b){return a + b;} int not_locking (int a, int b){return a + b;} private static final int ITERATIONS = 1000000; static public void main(String[] args) { synch tester = new synch(); double start = new Date().getTime(); for(long i = ITERATIONS; --i >= 0 ;) tester.locking(0,0); double end = new Date().getTime(); double locking_time = end - start; start = new Date().getTime(); for(long i = ITERATIONS; --i >= 0 ;) tester.not_locking(0,0); end = new Date().getTime(); double not_locking_time = end - start; double time_in_synchronization = locking_time - not_locking_time; System.out.println( "Time lost to synchronization (millis.): " + time_in_synchronization ); System.out.println( "Locking overhead per call: " + (time_in_synchronization / ITERATIONS) ); System.out.println( not_locking_time/locking_time * 100.0 + "% increase" );
  • 7. } } Though the HotSpot VM is supposed to address the synchronization-overhead problem, HotSpot isn't a freebee -- you have to buy it. Unless you license and ship HotSpot with your app, there's no telling what VM will be on the target platform, and of course you want as little as possible of the execution speed of your program to be dependent on the VM that's executing it. Even if deadlock problems (which I'll discuss in the next installment of this series) didn't exist, the notion that you should "synchronize everything" is just plain wrong-headed. Concurrency versus parallelism The next OS-related issue (and the main problem when it comes to writing platform- independent Java) has to do with the notions of concurrency and parallelism. Concurrent multithreading systems give the appearance of several tasks executing at once, but these tasks are actually split up into chunks that share the processor with chunks from other tasks. The following figure illustrates the issues. In parallel systems, two tasks are actually performed simultaneously. Parallelism requires a multiple-CPU system. Unless you're spending a lot of time blocked, waiting for I/O operations to complete, a program that uses multiple concurrent threads will often run slower than an equivalent single-threaded program, although it will often be better organized than the equivalent single-thread version. A program that uses multiple threads running in parallel on multiple processors will run much faster. Page 2 of 6
  • 8. Though Java permits threading to be implemented entirely in the VM, at least in theory, this approach would preclude any parallelism in your application. If no operating-system-level threads were used, the OS would look at the VM instance as a single-threaded application, which would most likely be scheduled to a single processor. The net result would be that no two Java threads running under the same VM instance would ever run in parallel, even if you had multiple CPUs and your VM was the only active process. Two instances of the VM running separate applications could run in parallel, of course, but I want to do better than that. To get parallelism, the VM must map Java threads through to OS threads; so, you can't afford to ignore the differences between the various threading models if platform independence is important. Get your priorities straight I'll demonstrate the ways the issues I just discussed can impact your programs by comparing two operating systems: Solaris and Windows NT. Java, in theory at least, provides ten priority levels for threads. (If two or more threads are both waiting to run, the one with the highest priority level will execute.) In Solaris, which supports 231 priority levels, this is no problem (though Solaris priorities can be tricky to use -- more on this in a moment). NT, on the other hand, has seven priority levels available, and these have to be mapped into Java's ten. This mapping is undefined, so lots of possibilities present themselves. (For example, Java priority levels 1 and 2 might both map to NT priority level 1, and Java priority levels 8, 9, and 10 might all map to NT level 7.) NT's paucity of priority levels is a problem if you want to use priority to control scheduling. Things are made even more complicated by the fact that priority levels aren't fixed. NT provides a mechanism called priority boosting, which you can turn off with a C system call, but not from Java. When priority boosting is enabled, NT boosts a thread's priority by an indeterminate amount for an indeterminate amount of time every time it executes certain I/O-related system calls. In practice, this means that a thread's priority level could be higher than you think because that thread happened to perform an I/O operation at an awkward time. The point of the priority boosting is to prevent threads that are doing background processing from impacting the apparent responsiveness of UI-heavy tasks. Other operating systems have more-sophisticated algorithms that typically lower the priority of background processes. The downside of this scheme, particularly when implemented on a per-thread rather than a per- process level, is that it's very difficult to use priority to determine when a particular thread will run. It gets worse. In Solaris, as is the case in all Unix systems, processes have priority as well as threads. The threads of high-priority processes can't be interrupted by the threads of low-priority processes. Moreover, the priority level of a given process can be limited by a system administrator so that a user process won't interrupt critical OS processes. NT supports none of this. An NT process is
  • 9. just an address space. It has no priority per se, and is not scheduled. The system schedules threads; then, if a given thread is running under a process that isn't in memory, the process is swapped in. NT thread priorities fall into various "priority classes," that are distributed across a continuum of actual priorities. The system looks like this: Windows NT's priority architecture The columns are actual priority levels, only 22 of which must be shared by all applications. (The others are used by NT itself.) The rows are priority classes. The threads running in a process pegged at the idle priority class are running at levels 1 through 6 and 15, depending on their assigned logical priority level. The threads of a process pegged as normal priority class will run at levels 1, 6 through 10, or 15 if the process doesn't have the input focus. If it does have the input focus, the threads run at levels 1, 7 through 11, or 15. This means that a high-priority thread of an idle priority class process can preempt a low-priority thread of a normal priority class process, but only if that process is running in the background. Notice that a process running in the "high" priority class only has six priority levels available to it. The other classes have seven. NT provides no way to limit the priority class of a process. Any thread on any process on the machine can take over control of the box at any time by boosting its own priority class; there is no defense against this. The technical term I use to describe NT's priority is unholy mess. In practice, priority is virtually worthless under NT. So what's a programmer to do? Between NT's limited number of priority levels and it's uncontrollable priority boosting, there's no absolutely safe way for a Java program to use priority levels for scheduling. One workable compromise is to restrict yourself to Thread.MAX_PRIORITY, Thread.MIN_PRIORITY, and Thread.NORM_PRIORITY when you call setPriority(). This restriction at least avoids the 10-levels-mapped-to-7-levels problem. I suppose you could use the os.name system property to detect NT, and then call a native method to turn off priority boosting, but that won't work if your app is running under Internet Explorer unless you also use Sun's VM plug-in. (Microsoft's VM uses a nonstandard native-method implementation.) In any event, I hate to use native methods. I usually avoid the problem as much as possible by putting most threads at NORM_PRIORITY and using scheduling mechanisms other than priority. (I'll discuss some of these in future installments of this series.)
  • 10. Cooperate! There are typically two threading models supported by operating systems: cooperative and preemptive. The cooperative multithreading model In a cooperative system, a thread retains control of its processor until it decides to give it up (which might be never). The various threads have to cooperate with each other or all but one of the threads will be "starved" (meaning, never given a chance to run). Scheduling in most cooperative systems is done strictly by priority level. When the current thread gives up control, the highest-priority waiting thread gets control. (An exception to this rule is Windows 3.x, which uses a cooperative model but doesn't have much of a scheduler. The window that has the focus gets control.) The main advantage of cooperative multithreading is that it's very fast and has a very low overhead. For example, a context swap -- a transfer of control from one thread to another -- can be performed entirely by a user-mode subroutine library without entering the OS kernel. (In NT, which is something of a worst-case, entering the kernel wastes 600 machine cycles. A user-mode context swap in a cooperative system does little more than a C setjump/longjump call would do.) You can have thousands of threads in your applications significantly impacting performance. Since you don't lose control involuntarily in cooperative systems, you don't have to worry about synchronization either. That is, you never have to worry about an atomic operation being interrupted. The main disadvantage of the cooperative model is that it's very difficult to program cooperative systems. Lengthy operations have to be manually divided into smaller chunks, which often must interact in complex ways. Page 4 of 6 The preemptive multithreading model The alternative to a cooperative model is a preemptive one, where some sort of timer is used by the operating system itself to cause a context swap. The interval between timer ticks is called a time slice. Preemptive systems are less efficient than cooperative ones because the thread management must be done by the operating-system kernel, but they're easier to program (with the exception of synchronization issues) and tend to be more reliable since starvation is less of a problem. The most important advantage to preemptive systems is parallelism. Since cooperative threads are scheduled by a user-level subroutine library, not by the OS, the best you can get with a cooperative model is concurrency. To get parallelism, the OS must do the scheduling. Of course, four threads running in parallel will run much faster than the same four threads running concurrently. Some operating systems, like Windows 3.1, only support cooperative multithreading. Others, like NT, support only preemptive threading. (You can simulate cooperative threading in NT with a user-mode library like the "fiber" library, but fibers aren't fully integrated into the OS.) Solaris
  • 11. provides the best (or worst) of all worlds by supporting both cooperative and preemptive models in the same program. Mapping kernel threads to user processes The final OS issue has to do with the way in which kernel-level threads are mapped into user- mode processes. NT uses a one-to-one model, illustrated in the following picture. NT user-mode threads effectively are kernel threads. They are mapped by the OS directly onto a processor and they are always preemptive. All thread manipulation and synchronization are done via kernel calls (with a 600-machine-cycle overhead for every call). This is a straightforward model, but is neither flexible nor efficient. The Solaris model, pictured below, is more interesting. Solaris adds to the notion of a thread, the notion of a lightweight process (LWP). The LWP is a schedulable unit on which one or more threads can run. Parallel processing is done on the LWP level. Normally, LWPs reside in a pool, and they are assigned to particular processors as necessary. An LWP can be "bound" to a specific processor if it's doing something particularly time critical, however, thereby preventing other LWPs from using that processor. Up at the user level, you have a system of cooperative, or "green," threads. In a simple situation, a process will have one LWP shared by all the green threads. The threads must yield control to each other voluntarily, but the single LWP the threads share can be preempted by an LWP in another process. This way the processes are preemptive with respect to each other (and
  • 12. can execute in parallel), but the threads within the process are cooperative (and execute concurrently). A process isn't limited to a single LWP, however. The green threads can share a pool of LWPs in a single process. The green threads can be attached (or "bound") to an LWP in two ways: Page 5 of 6 1. The programmer explicitly "binds" one or more threads to a specific LWP. In this case, the threads sharing a LWP must cooperate with each other, but they can preempt (or be preempted by) threads bound to a different LWP. If every green thread was bound to a single LWP, you'd have an NT-style preemptive system. 2. The threads are bound to green threads by the user-mode scheduler. This is something of a worst case from a programming point of view because you can't assume a cooperative or a preemptive environment. You may have to yield to other threads if there's only one LWP in the pool, but you might also be preempted.
  • 13. This threading model gives you an enormous amount of flexibility. You can choose between an extremely fast (but strictly concurrent) cooperative system, a slower (but parallel) preemptive system, or any combination of the two. So why does this matter to a Java programmer? The main issue is that the choice of threading model is entirely up to the VM -- you have no control. For example, early versions of the Solaris VM were strictly cooperative. Java threads were all green threads sharing a single LWP. The current version of the Solaris VM, however, uses several LWPs. Similarly, the NT VMs don't have the equivalent of green threads, so they're always preemptive. In order to write platform- independent code, you must make two seemingly contradictory assumptions: 1. You can be preempted by another thread at any time. You must use the synchronized keyword carefully to assure that non-atomic operations work correctly. 2. You will never be preempted unless you give up control. You must occasionally perform some operation that will give control to other threads so they can have a chance to run. Use yield() and sleep() in appropriate places (or make blocking I/O calls). For example, you might want to consider calling yield() every one hundred iterations or so of a long loop, or voluntarily going to sleep for a few milliseconds every so often to give lower-priority threads a chance to run. (yield() will yield control only to threads running at your priority level or higher). Wrapping it up So, those are the main OS-level issues you must consider when you're writing a Java program. Since you can make no assumptions about your operating environment, you have to program for the worst case. For example, you have to assume you can be preempted at any time, so you must use synchronized appropriately, but you must also assume that you will never be preempted, so you must also use yield(), sleep(), or occasionally blocking I/O calls to permit other threads to run. You can't assume priority levels 1 and 2 are different. They might not be after NT has mapped Java's 10 levels into its 7 levels. You can't assume that a priority level 2 thread will always be higher priority than one that runs at level 1. Subsequent articles will get into considerable detail about various thread-related programming problems and solutions. Here's the roadmap for the rest of the series: About the author Allen Holub has been working in the computer industry since 1979. He is widely published in magazines (Dr. Dobb's Journal, Programmers Journal, Byte, and MSJ, among others). He has seven books to his credit, and is currently working on an eighth that will present the complete sources for a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now
  • 14. looks at C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming (first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University of California, Berkeley Extension, since 1982. Allen offers both public classes and in- house training in Java and object-oriented design topics. He also does object-oriented design consulting. Get information, and contact Allen, via his Web site http://www.holub.com. Page 6 of 6 1. Deadlock, starvation, and nested-monitor lockout 2. Roll-your-own mutexes and a deadlock-handling lock manager 3. Counting semaphores, condition variables, and singletons 4. Event notification in a multithreaded environment (the mysteries of the AWTEventMulticaster) 5. Reader/writer locks 6. Timers 7. Synchronous-dispatching: multithreading without threads 8. Implementing the active-object pattern Resources For a great in-depth look at multithreading in general and the implementation of multithreading both in and with Java in particular, this one's a must. It's required reading if you're using threads heavilyDoug Lea, Concurrent Programming in JavaDesign Principles and Patterns (Addison Wesley, 1997). http://java.sun.com/docs/books/cp/ For an intro-level book on Java threading that is less technical but more readable than Lea's effort, seeScott Oaks and Henry Wong, Java Threads (O'Reilly, 1997) http://www.oreilly.com/catalog/jthreads/ This book is good for those looking into the general subject of multithreading, but doesn't have a Java slantBill Lewis and Daniel J. Berg, Threads PrimerA Guide to Multithreaded Programming (Prentice Hall/SunSoft Press, ISBN 0-13-443698-9) http://www.sun.com/books/books/Lewis/Lewis.html Doug Schmidt's ACE framework is a good, though complex, attempt at a truly platform- independent threading system http://www.cs.wustl.edu/~schmidt/
  • 15. Programming Java threads in the real world, Part 2 The perils of race conditions, deadlock, and other threading problems In his recent " Design for thread safety" article (part of the Design Techniques column), Bill Venners introduced the notion of a race condition, a situation whereby two threads simultaneously contend for the same object and, as a consequence, leave the object in an undefined state. Bill pointed out that Java's synchronized keyword is in the language to help avoid these problems, and he provided a straightforward example of its use. Last month's article was the first in a series on threads. The present article continues that series, covering race conditions in depth. It also discusses various deadlock scenarios, which are related to race conditions but are much harder to find and debug. This month, we'll focus primarily on the problems encountered when programming Java threads. Subsequent Java Toolbox columns will focus entirely on solutions. Monitors, mutexes, and bathrooms Just to make sure we're all starting from the same place, a little review of terms is in order. The central concept for synchronization in the Java model is the monitor, developed some 20 years ago by C. A. R. Hoare. A monitor is a body of code guarded by a mutual-exclusion semaphore (or, to use a term coined at Digital Equipment Corp., a mutex). The central notion of a mutex concerns ownership. Only one thread can own the mutex at a time. If a second thread tries to "acquire" ownership, it will block (be suspended) until the owning thread "releases" the mutex. If several threads are all waiting to acquire the same mutex, they will all be released simultaneously when the owning thread releases the mutex. The released threads will then have to sort out amongst themselves who gains ownership. (Typically, priority order, FIFO order, or some combination thereof is used to determine which thread gains control.) You guard a block of code by acquiring a mutex at the top of the block and releasing it at the bottom. The code comprising the monitor does not have to be contiguous: several discontiguous code blocks can all acquire and release the same mutex, in which case all of this code is considered to be in the same monitor because it uses a common mutex. The best analogy I've heard for a monitor is an airplane bathroom. Only one person can be in the bathroom at a time (we hope). Everybody else is queued up in a rather narrow aisle waiting to use it. As long as the door is locked, the bathroom is unaccessible. Given these terms, in our analogy the object is the airplane, the bathroom is the monitor (assuming there's only one bathroom), and the lock on the door is the mutex. In Java, every object has one and only one monitor and mutex associated with it. The single monitor has several doors into it, however, each indicated by the synchronized keyword. When a thread passes over the synchronized keyword, it effectively locks all the doors. Of course, if a
  • 16. thread doesn't pass across the synchronized keyword, it hasn't locked the door, and some other thread is free barge in at any time. Bear in mind that the monitor is associated with the object, not the class. Several threads can all be executing the same method in parallel, but the receiving objects (as identified by the this references) have to be different. For example, several instances of a thread-safe queue could be in use by several threads. These threads could simultaneously enqueue objects to different queues, but they could not enqueue to the same queue at the same time. Only one thread at a time can be in the monitor for a given queue. Page 2 of 7 To refine the earlier analogy, the airplane is still the object, but the monitor is really all the bathrooms combined (each synchronized chunk of code is a bathroom). When a thread enters any bathroom, the doors on all the bathrooms are locked. Different instances of a class are different airplanes, however, and if the bathroom doors in your airplane are unlocked, you needn't really care about the state of the doors in the other airplanes. Why is stop() deprecated in JDK 1.2? The fact that the monitor is built into every Java object is actually somewhat controversial. Some of the problems associated with bundling the condition variable and mutex together inside every Java object have been fixed in JDK 1.2, by the simple expediency of deprecating the most problematic methods of the Thread class: stop() and suspend(). You can deadlock a thread if you call either of these methods from inside a synchronized method of your own. Look at the following method, remembering that the mutex is the lock on the door, and the thread that locks the door "owns" the monitor. That's why the stop() and suspend() methods are deprecated in JDK 1.2. Consider this method: class some_class { //... synchronized void f() { Thread.currentThread().stop(); } } Now consider what happens when a thread calls f(). The thread acquires the lock when entering the monitor but then stops. The mutex is not released by stop(). This is the equivalent of someone going into the bathroom, locking the door, and flushing himself down the toilet! Any other thread that now tries to call f() (or any other synchronized method of the class) on the same object is blocked forever. The suspend() method (which is also deprecated) has the same problem. The sleep() method (which is not deprecated) can be just as tricky. (Someone goes into the bathroom, locks the door, and falls asleep). Also remember that Java objects, even those that extend Thread or implement Runnable, continue to exist, even if the associated thread has
  • 17. stopped. You can indeed call a synchronized method on an object associated with a stopped thread. Be careful. Race conditions and spin locks A race condition occurs when two threads try to access the same object at the same time, and the behavior of the code changes depending on who wins. The following diagram shows a single (unsynchronized) object accessed simultaneously by multiple threads. A thread can be preempted in fred() after modifying one field but before modifying the other. If another thread comes along at that point and calls any of the methods shown, the object will be left in an unstable state, because the initial thread will eventually wake up and modify the other field. Usually, you think of objects sending messages to other objects. In multithreaded environments, you must think about message handlers running on threads. Think: this thread causes this object to send a message to that object. A race condition can occur when two threads cause messages to be sent to the same object at the same time. Page 3 of 7 Working with wait() and notify() The following code demonstrates a blocking queue used for one thread to notify another when some event occurs. (We'll see a more realistic version of these in a future article, but for now I
  • 18. want to keep things simple.) The basic notion is that a thread that tries to dequeue from an empty queue will block until some other thread puts something in the queue. class Notifying_queue { private static final queue_size = 10; private Object[] queue = new Object[ queue_size ]; private int head = 0; private int tail = 0; public void synchronized enqueue( Object item ) { queue[++head %= queue_size ]= item; this.notify(); // The "this" is there only to } // improve readability. public Object synchronized dequeue( ) { try { if( head == tail ) // <=== This is a bug this.wait(); } catch( InterruptedException e ) { // If we get here, we were not actually notified. // returning null doesn't indicate that the // queue is empty, only that the waiting was // abandoned. return null; } return queue[++tail %= queue_size ]; } } Starting with an empty queue, let's follow the sequence of operations in play if one thread does a dequeue and then another (at some later time) enqueues an item. 1. The dequeueing thread calls dequeue(), entering the monitor (and locking out other threads) until the monitor is released. The thread tests for an empty queue (head==tailwait(). (I'll explain why this is a bug in a moment.) 2. The wait() releases the lock. (The current thread temporarily leaves the monitor.) It then blocks on a second synchronization object called a condition variable. (The basic notion of a condition variable is that a thread blocks until some condition becomes true. In the case of Java's built-in condition variable, the thread will wait until the notified condition is set to true by some other thread calling notify.) It's important to realize that the waiting thread has released the monitor at this juncture. 3. A second thread now comes along and enqueues an object, eventually calling notify(), thereby releasing the waiting (dequeueing) thread.
  • 19. 4. The dequeueing thread now has to wait to reacquire the monitor before wait() can return, so it blocks again, this time on the lock associated with the monitor. 5. The en queueing thread returns from the enqueue() method releasing the monitor. 6. The dequeueing thread acquires the monitor, wait() returns, an object is dequeued, and dequeue() returns, releasing the monitor. Race conditions following a wait() Now, what sorts of problems can arise? The main ones are unexpected race conditions. First, what if you replaced the notify() call with a call to notifyAll()? Imagine that several threads were simultaneously trying to dequeue something from the same, empty, queue. All of these threads are blocked on a single condition variable, waiting for an enqueue operation to be executed. When enqueue() sets the condition to true by calling notifyAll(), the threads are all released from the wait (moved from a suspended to a runnable state). The released threads all try to acquire the monitor at once, but only one would "win" and get the enqueued object. The problem is that the others would then get the monitor too, in some undefined sequence, after the winning thread had dequeued the object and returned from dequeue(). Since these other threads don't retest for an empty queue, they will dequeue garbage. (Looking at the code, the dequeue() method will advance the tail pointer to the next slot, the contents of which are undefined since the queue is empty.) Page 4 of 7 Fix the problem by replacing the if statement with a while loop (called a spin lock). Now the threads that don't get the object will go back to waiting: public Object synchronized dequeue( ) { try { while( head == tail ) // used to be an if this.wait(); } catch( InterruptedException e ) { return null; } return queue[++tail %= queue_size ]; }
  • 20. That while loop also solves another, less obvious problem. What if we leave the notify() statement in place, and don't put in a notifyAll()? Since notify() releases only one waiting thread, won't that solve the problem? It turns out that the answer is no. Here's what can happen: 1. notify() is called by the enqueueing thread, releasing the condition variable. 2. The dequeueing thread is then preempted, after being released from the wait on the condition variable, but before it tries to reacquire the monitor's lock. 3. A second thread calls dequeue() at exactly this point, and successfully enters the monitor because no other threads are officially waiting. This second thread successfully dequeues the object; wait() is never called since the queue isn't empty. 4. The original thread is now allowed to run, it acquires the monitor, doesn't test for empty a second time, and then dequeues garbage. This second scenario is easily fixed the same way as the first: replace the if statement with a while loop. Note that the Java specification does not require that wait() be implemented as an atomic operation (that is, one that can't be preempted while moving from the condition variable to the monitor's lock). This means that using an if statement instead of a while loop might work in some Java implementations, but the behavior is really undefined. Using a spin lock instead of a simple if statement is cheap insurance against implementations that don't treat wait() as atomic. Threads are not objects Now let's move on to harder-to-find problems. The first difficulty is the commonplace confusion of threads and objects: Methods run on threads, objects do not. Put another way, the only way to get a method to run on a given thread is to call it (either directly or indirectly) from that thread's run() method. Simply putting it into the Thread derivative is not enough. For example, look at the following simple thread (which just prints its fields every so often): class My_thread extends Thread { private int field_1 = 0; private int field_2 = 0; public void run() { setDaemon(true); // this thread will not keep the app alive while( true ) { System.out.println( " field_1=" + field_1 " field_2=" + field_2 );
  • 21. sleep(100); } } synchronized public void modify( int new_value ) { field_1 = new_value; field_2 = new_value; } } You could start up the thread and send it a message like this: My_thread test = new My_thread; test.start(); //... test.modify(1); The only functions that run on the new thread are run() itself and println() (which run() calls). The modify() method never runs on the same thread as the println call; rather, it runs on whatever thread was running when the call was made. (In this case, it runs on whatever thread main() is running on.) Depending on timing, the earlier fragment could print: Page 5 of 7 field_1=0, field2=0 But it could just as easily print: field_1=0, field2=1 or field_1=1, field2=1 There's just no telling. (In the first and last cases, the thread would have been outside the println statement when modify() was called. In the second example, the thread would have been halfway through evaluating the arguments to println(), having fetched field_1, but not field_2. It prints the unmodified field_1 and the modified field_2. The main issue here is that there is no simple solution to this problem. The modify() method is indeed synchronized in the earlier example, but run() can't be. Were it synchronized, you'd enter the monitor (and lock the object), when you started up the thread. Thereafter, any other thread that called any synchronized method on the object (such as modify()) would block until the
  • 22. monitor was released. Since run() doesn't return (as is often the case), the release will never happen, and the thread will act like a black hole, sucking up any other thread that calls any of its synchronized methods. In the current example, the main thread would be suspended, and the program would hang. So just using the synchronized keyword in a naive way gets us nowhere. The deadlock scenario Synchronizing run() is a good example of a simple deadlock scenario, where a thread is blocked forever, waiting for something to happen that can't. Let's look at a few examples that are more realistic than this. The most common deadlock scenario occurs when two threads are both waiting for each other to do something. The following (admittedly contrived) code snippet makes what's going on painfully obvious: class Flintstone { int field_1; private Object lock_1 = new int[1]; int field_2; private Object lock_2 = new int[1]; public void fred( int value ) { synchronized( lock_1 ) { synchronized( lock_2 ) { field_1 = 0; field_2 = 0; } } } public void barney( int value ) { synchronized( lock_2 ) { synchronized( lock_1 ) { field_1 = 0; field_2 = 0; } } } } Now, imagine a scenario whereby one thread (call it Wilma) calls fred(), passes through the synchronization of lock_1, and is then preempted, allowing another thread (call it Betty) to execute. Betty calls barney(), acquires lock_2, and tries to acquire lock_1, but can't because Wilma has it. Betty is now blocked, waiting for lock_1 to become available, so Wilma wakes up and tries to acquire lock_2 but can't because Betty has it. Wilma and Betty are now deadlocked. Neither one can ever execute.
  • 23. (Note that lock_1 and lock_2 have to be one-element arrays rather than simple ints, because only objects have monitors in Java; the argument to synchronized must be an object. An array is a first- class object in Java; a primitive-type such as int is not. Consequently, you can synchronize on it. Moreover, a one-element array is efficient to bring into existence compared to a more elaborate object (like an Integer) since it's both small and does not require a constructor call. Also, note that I can keep the reference to the lock as a simple Object reference, since I'll never access the array elements.) Page 6 of 7 As I mentioned above, Wilma and Betty are a contrived example, but the multiple-lock situation comes up frequently. I'll give a more detailed example next month. Get out the magnifying glass If all deadlock scenarios were as easy to recognize as Wilma and Betty, deadlock wouldn't be a problem. Consider the following code, though: class Boss { private Side_kick robin; public synchronized void set_side_kick( Side_kick kid_in_tights ) { robin = kid_in_tights; }; public synchronized void to_the_bat_cave() { robin.get_in_the_car(); } public synchronized void okay() // sent to us by robin { //... } public synchronized void hold_on() // sent to us by robin { //... } } //------------------------------------------------------- class Side_kick { private Boss batman; public synchronized void set_boss( Boss guy_in_cape ) { batman = guy_in_cape; } public synchronized void get_in_the_car() // sent by batman { batman.okay(); } public synchronized void sock_bam_pow() // sent from outside { batman.hold_on(); } } //-------------------------------------------------------
  • 24. class Gotham_city { static Boss batman = new Boss(); static Side_kick robin = new Side_kick(); public static void main( String[] args ) { batman.set_side_kick( robin ); robin.set_boss( batman ); // spawn off a bunch of threads that use batman and robin. } } Now imagine the following: 1. One thread (call it Alfred) issues a to_the_bat_cave() request to the batman object passed to it from main(). 2. The batman object starts to process the method, but is preempted just before it calls robin.get_in_the_car(). At this juncture, Alfred has acquired the lock for the batman object. 3. Now along comes a second thread (call it Joker), which issues a sock_bam_pow() message to the robin object that it got from main(). 4. The robin object (whose sock_bam_pow() method is running on the Joker thread) tries to send a hold_on() message to batman, but can't because Alfred owns the lock on batman. So the Joker thread is now blocked, waiting for Alfred to release the lock on batman. 5. Now Alfred gets a chance to run, and it tries to send a get_in_the_car() message to the robin object, but it can't because the Joker thread owns the lock on robin. Both threads are now deadlocked (sound familiar?) Remember: threads own the locks and methods execute on threads, not objects. This situation is, of course, much harder to see than the Wilma-and-Betty problem because the locks in the batman-robin example are the natural locks associated with individual objects. There are no standalone synchronized statements in the batman-robin code, and the locks are associated with two completely distinct objects. Multithreaded programmers tear their hair out looking for the causes of these sorts of problems, and there are only two solutions. The first is to thoroughly design the code before implementing it, and then to really study both the design and the implementation before you even think about running it the first time. When I do multithreaded programs, I spend much more time on code and design reviews than I spend on coding.
  • 25. Page 7 of 7 The other solution is to use an architecture that tends to minimize these sorts of problems. (Various threading architectures will be the subject of the remaining articles in this series.) There is no magic bullet there. I've seen ads for a couple of products that instrument a VM in a way that, in theory, will detect deadlock and race conditions. The problem, though, is a classic Heisenberg-uncertainty dilemma: there's no way to observe the process without impacting it. If the problem is timing-related, adding a print statement or changing to a debugging version of the VM will change the timing, perhaps eliminating the problem. I haven't actually used any of these products yet, but I remain skeptical. Nested-monitor lockout Another important form of deadlock is not discussed much in the Java-language books: nested- monitor lockout. This problem usually occurs when you call a blocking function from within a synchronized method, and the only way to release the block is to call another synchronized method. The following code demonstrates the problem. class Notifying_queue { // Same class as was described earlier, blocks on dequeue from // an empty queue. //... } class Black_hole { private Notifying_queue queue = new Notifying_queue(5); public synchronized void put( Object thing ) { queue.enqueue( thing ); } public synchronized Object get( ) { return queue.dequeue(); } } Consider what happens when you try to dequeue something from an empty queue: 1. You call get() to get the item from the queue. 2. get() is synchronized, so the Black_hole is now locked. 3. get() calls dequeue(), which blocks, waiting for some other thread to enqueue something. get() does not return.
  • 26. 4. Another thread tries to enqueue something, but the only way to enqueue something is by calling put, which we can't do because the Black_hole is locked. That is, any thread that tries to put() will block because the first thread has not returned from get() yet. The Black_hole now sucks up all threads that try to put() or get() anything. They all block forever. Depending on where this occurs, the black hole could suck up every thread in your program. Also bear in mind that this problem can occur anytime you have a blocking call (such as a file read) inside a synchronized method. The only cures are: 1. Don't make blocking calls in synchronized methods 2. Make sure there's a way to talk to the blocking object via another class or a nonsynchronized method In the current situation, you could just not synchronize put(), but that wouldn't work in a more realistic situation where put() accessed fields of the class that were accessed by other methods. This problem has been known since Java 1.0 was in the early prerelease stage, and several people complained vociferously about it. (The problem is a direct result of the way Java's synchronization works -- the condition variable and mutex are both part of the object and not separate entities --- compounded by the fact that you have to acquire the mutex to wait on the condition.) But as Doug Lea pointed out in a recent e-mail to me: [the complaints] boiled down to "you tend to like best what you are most used to." Java makes some things that are painful in POSIX easy, and vice-versa. In any case, it is pretty easy to simulate one set of primitives with the other. That's life, I guess. The next several articles in this series on threads will present a solution to the problem that decouples the semaphores from the things they guard, but that solution introduces a whole set of additional problems. Conclusion
  • 27. Hopefully, I've demonstrated by now that programming in a multithreaded environment isn't as easy as the evangelist types would have you believe. Java provides platform-independent ways to use the two essential synchronization mechanisms: exclusion semaphores and condition variables. It does it in an awkward way, however, that doesn't help much when you're trying to do more than blink your logo in a simple applet. All is not lost, however. Over the next few months, I'll present a library of classes that solve many common threading problems, including some of the ones I've just discussed. Sometimes, I even think of it as fun, but maybe that's because I've been programming too long. About the author Allen Holub has been working in the computer industry since 1979. He is widely published in magazines (Dr. Dobb's Journal, Programmers Journal, Byte, MSJ, among others). He has seven books to his credit, and is currently working on an eighth that will present the complete sources for a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now looks at C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming (first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University of California Berkeley Extension since 1982. Allen offers both public classes and in-house training in Java and object-oriented design topics. He also does object-oriented design consulting. Get more information and contact Allen via his Web site http://www.holub.com. Resources Sun's Technical Articles page has several articles on multithreading http://developer.javasoft.com/developer/technicalArticles/#thread Prashant Jain and Douglas C. Schmidt have a good article contrasting C++ to Java that discusses many of the thread-related problems inherent in the language. The article can be found at http://www.cs.wustl.edu/%7Eschmidt/C++2java.html Doug Lea has a bunch of Mutex and Condition-variable classes in his util.concurrent package. See http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html Doug Schmidt's Ace Framework is a good, though complex, attempt at a truly platform- independent threading system http://www.cs.wustl.edu/~schmidt/ There are several good books that discuss the Java threading issues mentioned in the first article in this series. For convenience, I've listed them again here: For a great in-depth look at multithreading in general and the implementation of multithreading both in and with Java in particular, this is a must. It's required reading if you're using threads heavilyDoug Lea, Concurrent Programming in JavaDesign Principles and Patterns (ReadingAddison Wesley, 1997) http://java.sun.com/docs/books/cp/ For a book on Java threading that is less technical but more readable than Lea's, seeScott Oaks and Henry Wong, Java Threads (Sebastopol, Calif.O'Reilly, 1997) http://www.oreilly.com/catalog/jthreads/
  • 28. This book is good for the general subject of multithreading but doesn't have a Java slantBill Lewis and Daniel J. Berg, Threads PrimerA Guide to Multithreaded Programming (Englewood CliffsPrentice Hall/SunSoft Press, ISBN 0-13-443698-9) http://www.sun.com/books/books/Lewis/Lewis.html
  • 29. Programming Java threads in the real world, Part 3 Roll-your-own mutexes and centralized lock management By Allen Holub, JavaWorld.com, 11/01/98 In last month's column, I demonstrated a simple deadlock scenario using two nested synchronized blocks that acquired the same two locks, but in a different order. (Please review last month's example if this isn't fresh in your mind.) This month, I'll take a look at a solution to this commonplace deadlock problem, presenting a roll-your-own exclusion semaphore class and a lock manager that supports the safe acquisition of multiple semaphores. Using these objects rather than the built-in synchronized can save you hours of searching for unexpected deadlocks. (They don't solve every possible deadlock problem, of course, but are nonetheless pretty useful.) When 'synchronized' isn't good enough The nested-synchronized-statements example from last month was -- admittedly -- contrived, but the multiple-lock situation comes up frequently in the real world. One common problem is the too-coarse, object-level granularity of the synchronized keyword: there's only one monitor per object, and sometimes that's not enough. Consider the following class, the methods of which can be broken up into three partitions. The methods in the first partition use only a subset of the fields in the class. The methods in the second partition use a non-overlapping subset; they share fields that are not used by methods in the first partition. The methods in the third partition use everything. 1| class Complicated // NOT thread safe 2| { 3| private long a, b; 4| private long x, y; 5| 6| // partition 1, functions use a and/or b 7| 8| public void use_a() { do_something_with(a); ); } 9| public void use_b() { do_something_with(b); ); } 10 public void use_a_and_b() { do_something_with(a+b); ); } 11| 12| // partition 2, functions use x and/or y 13| 14| public void use_x() { do_something_with(x); ); } 15| public void use_y() { do_something_with(y); ); } 16| public void use_x_and_y() { do_something_with(x+y); ); } 17| 18| // partition 3, functions use a, b, x, and y 19| 20| public void use_everything() { do_something_with( a +
  • 30. x ); } 21| public void use_everything_else(){ do_something_with( b + y ); } 22| } As it stands, this code is a multithreading disaster. Nothing is synchronized and we have guaranteed race conditions. (A race condition occurs when two threads try to access the same object at the same time, and chance determines which one wins the "race." Programs shouldn't work by chance.) Synchronizing all the methods would fix the problem, but then you couldn't call a method in partition 1 (emphasized in the code above) simply because some thread was using a method from partition 2 above. Since these two partitions don't interact with each other, this solution imposes needless access restrictions on the methods of the class. If you're accessing any method in partition 3, though, you do want to lock out everything in the other two partitions. We really need two locks in this situation. One to lock partition-1 variables and another for partition-2 variables. The methods in partition 3 can then grab both locks. Page 2 of 5 So, roll your own You can implement the two-lock strategy by synchronizing on something other than the containing object. Rather than synchronizing the methods, you can define two local variables to use as locks and synchronize on them: 1| class Complicated_and_thread_safe 2| { 3| private long a, b; 4| private long x, y; 5| 6| private Object ab_lock = new int[]; 7| private Object xy_lock = new int[]; 8| 9| // partition 1, functions use a and/or b 10| 11| public void use_a() { synchronized(ab_lock){ /*...*/ } } 12| public void use_b() { synchronized(ab_lock){ /*...*/ } } 13| public void use_a_and_b(){ synchronized(ab_lock){ /*...*/ } } 14| 15| // partition 2, functions use x and/or y 16| 17| public void use_x() { synchronized(xy_lock){ /*...*/ } } 18| public void use_y() { synchronized(xy_lock){ /*...*/ } }
  • 31. 19| public void use_x_and_y(){ synchronized(xy_lock){ /*...*/ } } 20| 21| // partition 3, functions use a, b, x, and y 22| 23| public void use_everything() 24| { synchronized(ab_lock) // grab both locks 25| { synchronized(xy_lock) 26| { /*...*/ 27| } 28| } 29| } 30| 31| public void use_everything_else() 32| { synchronized(ab_lock) 33| { synchronized(xy_lock) 34| { /*...*/ 35| } 36| } 37| } 38| } I haven't synchronized the methods themselves in this example. (Remember, synchronizing a method is effectively the same thing as wrapping all the code in the method in a synchronized(this){...} block.) Instead, I'm providing a unique lock for each partition ( ab_lock and xy_lock) and then explicitly synchronizing on these individual locks. Java associates locks with objects (instance of some class that extends Object, so I can't use primitive-type variables as locks here. I don't want to spend unnecessary time calling constructors and doing other initialization operations on complicated objects, however. Consequently, the locks themselves are declared as the simplest possible Object -- an array. Arrays in Java are first-class objects: they implicitly extend the Object class. (If you don't believe me, compile the following code: 1| public class foo 2| { static Object ref = new int[]{ 10 }; 3| 4| public static void main( String[] args ) 5| { System.out.println( ref.toString() ); 6| } 7| } The class compiles just fine. Not only does the implicit cast from the array to Object work (because Object is a base class of all arrays), but the println() correctly invokes the compiler- generated toString() override (which prints absolutely nothing useful -- but you can't have
  • 32. everything). I've used a one-element array for my lock, rather than something like an Integer, because arrays come into existence very efficiently. For example, there's no constructor call. In the foregoing example, it's critical that methods that acquire both locks always acquire them in the same order, otherwise we end up in the Wilma-and-Betty deadlock scenario discussed last month. Acquiring multiple locks is a commonplace enough problem that some operating systems have system calls for this purpose. It would be nice to have an easy way to acquire multiple locks, in Java, without having to worry about the order-of-acquisition problem. The remainder of this month's column describes one way to do that. Page 3 of 5 Semaphores Listing 1 shows the core interface I use for all my roll-your-own semaphore classes: the Semaphore. It's in the com.holub.asynch package, as are all the thread-related classes and interfaces I'll be discussing. (I've also put all the code that appears in the listings into the "Goodies" section on my Web site; see Resources for the link.) 1| package com.holub.asynch; 2| 3| interface Semaphore 4| { 5| int id (); 6| void acquire(long timeout) throws InterruptedException; 7| void release(); 8| } Listing 1: Semaphore.java If you haven't worked much with semaphores, a semaphore has to have two properties in order to be useful: 1. It must be able to identify itself using a unique ID when passed an id() request. The current implementation uses a unique integer for the purpose. 2. You must be able to acquire and release the semaphore, though the semantic meaning of "acquire" and "release" can vary, depending on what sort of semaphore you're dealing with. Managing the locks
  • 33. Any semaphore that implements Semaphore can be locked in groups using the Lock_manager class shown in Listing 2. There are several things going on here. First of all, I've used the Arrays.sort() method, one of the new JDK 1.2 data-structure facilities, to make life easier. The Arrays class is a "Booch utility" -- a class that contains nothing but static methods. The java.lang.Math utility is another example. In fact, the Lock_manager itself is a utility, since it's made up solely of static methods. 01| package com.holub.asynch; 02| 03| import com.holub.asynch.Semaphore; 04| import java.util.Arrays; 05| import java.util.Comparator; 06| 07| class Lock_manager 08| { 09| private static int[] id_lock = new int[1]; 10| private static int id_pool = 0; 11| 12| public static int new_id( ) 13| { 14| int id; 15| synchronized( id_lock ) { 16| id = id_pool++; } 17| return id; 18| } 19| 20| /** 21| * Every mutex is given a unique int ID when it's created. 22| * This function returns once all of the locks in the incoming 23| * array have been successfully acquired. Locks are always 24| * acquired in ascending order of ID to attempt to avoid 25| * deadlock situations. 26| * 27| * @param locks All of these locks must be acquired before 28| * acquire_multiple returns. 29| * @param timeout Maximum time to wait to acquire each 30| * lock (milliseconds). The total time for the multiple 31| * acquire operation could be (timeout * locks.length). 32| **/ 33| 34| public static void acquire_multiple( Semaphore[] locks, long timeout ) 35| throws InterruptedException 36| { try 37| { 38| Arrays.sort(locks, new Lock_comparator() ); 39| for( int i = 0; i < locks.length; ++i ) 40| locks[i].acquire( timeout ) ; 41| } 42| catch( Comparable.Not_comparable e ) 43| { 44| // shouldn't happen. Convert it to an uncaught
  • 34. 45| // exception to terminate the program. 46| 47| throw new Error( e.toString() ); 48| } 49| } 50| 51| private static class Lock_comparator implements Comparator 52| { public int compare(Object a, Object b) 53| throws Comparable.Not_comparable 54| { return ((Semaphore)a).id() - ((Semaphore)b).id(); 55| } 56| public boolean equals(Object obj) 57| { return obj instanceof Lock_comparator; 58| } 59| } 60| } Listing 2: Lock_manager.java "Booch utilities" Digressing for a moment, in most object-oriented languages, utilities are kludges needed to get around design problems in the language itself or in existing libraries. The problem in Java is that neither arrays nor primitive types really are true objects. If they were, they would contain all the methods needed to manipulate them. For example, you would be able to find a cosine using: int x = PI; x.cosine(); You'd also be able to extend int, and so forth. In the case of arrays, you ought to be able to sort them by asking them to sort themselves, like this: int array[] = new int[]; //... array.sort(); Unfortunately, Java isn't a "pure" object-oriented language in which everything is an object, so we have to make do with a utility class. You can use the Arrays utility as follows to sort an array of int: int array[] = new int[]; //... Arrays.sort( array );
  • 35. The problem is complicated a bit by arrays of objects of some arbitrary class. How can a generic sort utility figure out the correct ordering? The Object class contains an equals() function, but we'd need a greater_than() as well to do sorting. The Strategy pattern To the rescue comes the "Strategy" pattern. The notion is to pass into a method or object another object that encapsulates the strategy needed to do something. There are lots of examples of Strategy in Java. A java.awt.LayoutManager, for example, encapsulates the strategy that a Frame uses for laying itself out. You can change the layout simply by changing this strategy. You don't have to define a new kind of Frame class using derivation or some other mechanism. This, in turn, makes Frame much more "reusable" since the behavior of a Frame can be changed without changing any of the code that comprises Frame. The Arrays utility uses the Strategy pattern to define a comparison strategy. In Listing 2, the Lock_comparator (at the bottom of the listing) implements the comparison strategy by implementing the new (to JDK 1.2) java.util.Comparator interface and its compare() method. (This works like C's strcmp(): it returns a negative number, zero, or a positive number depending on whether a is less than, equal to, or greater than b.) An instance of the Comparator is passed to the Arrays.sort() method like this: Arrays.sort(locks, new Lock_comparator() ); The Lock_comparator object's compare() method is called by Arrays.sort() when it needs to compare objects. C programmers will recognize this approach as very similar to that of qsort(), which is passed a pointer to a compare method -- another example of Strategy. How Lock_manager works Now, let's look at what the Lock_manager actually does. It starts out with the static new_id() method that returns a unique int every time it's called. This method will be called from the various objects that implement the Semaphore interface to get the value they'll use for their ID. The acquire multiple method is used to safely acquire groups of semaphores. You pass it an array of objects that implement Semaphore, it sorts the array by ID, and then acquires the semaphores one at a time in ID order. Consistent acquisition of multiple semaphores in ID order effectively eliminates the deadlock scenario we've been discussing. Page 4 of 5 A manageable mutex Now let's move on and look at the other side of the equation: a class that implements a semaphore. The Mutex class in Listing 3 implements a simple mutual-exclusion semaphore. Whether it makes sense to use it (as compared to synchronizing on a lock object, as I did
  • 36. earlier) really depends on the situation. The main advantage is in being able to use the Lock_manager.acquire_multiple() to acquire several mutexes without worrying about an order-of- acquisition deadlock. I personally think the code also looks a little cleaner. I prefer this: 1| class some_class 2| { 3| private Mutex lock = new Mutex(); 4| 5| public void f() 6| { lock.acquire(); 7| //... 8| lock.release(); 9| } 10| } to this: 1| class some_class 2| { 3| private Object lock = new int[1]; 4| 5| public void f() 6| { synchronized( lock ) 7| { 8| //... 9| } 10| } 11| } even though it's possible to forget the explicit release(). 1| package com.holub.asynch; 2| 3| import com.holub.asynch.Semaphore; 4| 5| import com.holub.tools.Comparable; 6| import com.holub.tools.Sort; 7| 8| // Implementation of a mutual-exclusion semaphore. It can be owned by 9| // only one thread at a time. The thread can acquire it multiple times, 10| // but there must be a release for every acquire. 11| 12| public class Mutex implements Semaphore 13| { 14| private Thread owner = null; // Owner of mutex, null if nobody 15| private int lock_count = 0; 16| 17| private final int _id = Lock_manager.new_id();
  • 37. 18| public int id() { return _id; } 19| 20| /** 21| * Acquire the mutex. The mutex can be acquired multiple times 22| * by the same thread, provided that it is released as many 23| * times as it is acquired. The calling thread blocks until 24| * it has acquired the mutex. (There is no timeout). 25| * 26| * @see release 27| * @see acquire_without_blocking 28| */ 29| 30| public synchronized void acquire( long timeout ) 31| throws InterruptedException 32| { while( acquire_without_blocking() == false) 33| this.wait( timeout ); 34| } 35| 36| /** 37| * Attempts to acquire the mutex. Returns false (and does not 38| * block) if it can't get it. 39| * 40| * @see release 41| * @see acquire 42| */ 43| 44| public synchronized boolean acquire_without_blocking() 45| { 46| // Try to get the mutex. Return true if you got it. 47| 48| if( owner == null ) 49| { owner = Thread.currentThread(); 50| lock_count = 1; 51| return true; 52| } 53| 54| if( owner == Thread.currentThread() ) 55| { ++lock_count; 56| return true; 57| } 58| 59| return false; 60| } 61| 62| /** 63| * Release the mutex. The mutex has to be released as many times 64| * as it was acquired to actually unlock the resource. The mutex 65| * must be released by the thread that acquired it 66| * 67| * @throws Mutex.OwnershipException (a RuntimeException) if a thread 68| * other than the current owner tries to release the mutex. 69| */ 70|
  • 38. 71| public synchronized void release() 72| { 73| if( owner != Thread.currentThread() ) 74| throw new OwnershipException(); 75| 76| if( --lock_count <= 0 ) 77| { owner = null; 78| notify(); 79| } 80| } 81| 82| public static class OwnershipException extends RuntimeException 83| { public OwnershipException() 84| { super("Thread calling release() doesn't own Mutex"); 85| } 86| } 87| } Listing 3: Mutex.java Admittedly, this preference for the explicit call to acquire() is largely a matter of personal taste. We'll look at a few other semaphore implementations next month that are a bit harder to simulate with a simple synchronized, however. Getting back to Listing 3, the Mutex class starts out with the stuff needed to keep the Lock_manager happy. It implements the Semaphore interface with an id() method that returns the value of the _id field, which in turn holds a unique value that came from the Lock_manager. There are two versions of the acquire method: acquire() itself and acquire_without_blocking(). The latter simply returns false if it can't acquire the mutex. If the mutex isn't owned, it sets owner to reference the thread that called acquire_without_blocking() with the call to Thread.currentThread(), which does the obvious. The blocking version, acquire(), calls the nonblocking version and suspends itself with a wait() call if it can't get the mutex right off the bat. Note the use of a spin lock here. (I discussed spin locks last month.) The wait() call is inside a loop in case some other thread breaks in at an inopportune moment and steals the mutex. Interestingly, the Mutex code doesn't actually use the Mutex object's monitor to implement the lock (though it does use the monitor to make sure that two threads don't try to acquire the same mutex simultaneously -- acquire() and release() are synchronized). A local field called owner is used to decide whether or not to block out an acquiring thread. The owner field references the Thread instance that contains the run() method for a given thread. If owner is null, the mutex is up for grabs, otherwise, some thread "owns" the mutex and any other thread that tries to acquire it will block at the wait() call in acquire().
  • 39. My Mutex class implements a "recursive" mutex. The "owner" thread can acquire the mutex more than once, but it must release the mutex by calling release() as many times as it acquired it by calling acquire(). This facility is essential when two methods both must acquire the mutex. In the following code, for example, g() calls f(), but f() can also be called from outside -- without going through g() first. If the mutex weren't recursive, the thread that called g() would block when g() called f() and tried to acquire the mutex a second time. As it is, the double acquisition isn't a problem since every acquire() has a corresponding release(). The lock_count field keeps track of the number of times the mutex has been locked by its owner. 1| class some_class 2| { Mutex lock = new Mutex(); 3| 4| public void f() 5| { lock.acquire(); 6| //... 7| lock.release(); 8| } 9| 10| public void g() 11| { lock.acquire(); 12| //... 13| f(); 14| //... 15| lock.release(); 16| } 17| } Until next time So that's a simple roll-your-own semaphore. Though it's easy enough to use Java's synchronized statement directly to do everything the Mutex does, the code is cleaner when you use the Mutex class, and the associated Lock_manager can solve a class of otherwise hard-to-manage deadlock problems. Page 5 of 5 Next month I'll look at a couple of other useful semaphores: a condition variable and a Djikstra counting semaphore. I'll also discuss critical sections and class-level locks. In future columns, we'll take a look at other thread-related classes, such as timers and reader/writer locks, and explore architectural solutions to threading-related problems. About the author
  • 40. Allen Holub has been working in the computer industry since 1979. He is widely published in magazines (Dr. Dobb's Journal, Programmers Journal, Byte, MSJ, among others). He has seven books to his credit, and is currently working on an eighth that will present the complete sources for a Java compiler written in Java. Allen abandoned C++ for Java in early 1996 and now looks at C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming (first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University of California Berkeley Extension since 1982. Allen offers both public classes and in-house training in Java and object-oriented design topics. He also does object-oriented design consulting. Get more information and contact Allen via his Web site http://www.holub.com. Resources All the real code (the stuff in the com.holub.asynch package) is available in the "Goodies" section on my Web site http://www.holub.com
  • 41. Programming Java threads in the real world, Part 4 Condition variables and counting semaphores -- filling in a few chinks in Java's threading model By Allen Holub, JavaWorld.com, 12/01/98 This month's column adds a few more classes to the threading arsenal we started building in last month's Java Toolbox column. This time I'll discuss the following: 1. Roll-your-own versions of the condition variable, which replaces wait() and notify() in some situations 2. Djikstra's "counting" semaphore, which is used to manage pools of resources (A semaphore is any of several mechanisms used to synchronize and communicate between threads. Think of the "semaphore" as in the flags that two boy scouts use to talk to each other from a distance -- different flag positions represent different letters of the alphabet. Napoleon's army used the vanes of windmills on mountain tops to send semaphore messages great distances very quickly. The mutex discussed last month, since it's a communications mechanism, is also a "semaphore.") The condition variable can often be simulated using Java alone -- and I'll show you how -- while the counting semaphore can't. Before I start, though, I'd like to go over a few loose ends from last month and fix a few bugs. Oops! Could my code have bugs in it? Before leaping into this month's meat (now there's a metaphor you can sink your teeth into), let's look at a few loose ends that either caught my eye (or were brought to my attention) after last month's article went live. During one of my book's "peer reviews," an academic reviewer once took exception to the sentence "if you're anything like me, you'll forget to ... so you should write your code to do it automatically." His comment to me was: "I would never admit that in print." This guy was (and as far as I know, still is) a tenured professor at an Ivy League university, and I suppose his comment was correct in a literal sense: since he never had written any actual code, he never had any bugs to admit. I might as well say it up front, though: my code contains an occasional bug (gasp). Consequently, I expect an "Oops" section or its moral equivalent to become a regular feature of this column. There's nothing like having 100,000 people look over your code for problems to emerge and be highlighted.
  • 42. Joe Bowbeer pointed out (quite correctly): Why not advise the use of try/finally to prevent an exception from gunking up the works? mutex.acquire(); try { doit(); } finally { mutex.release(); } I prefer the [above] form of try/finally because it separates the exceptions that might occur in changing the state (acquire) from the exceptions that might occur when working in the new state (doit). The other (more intuitive?) form is try { mutex.acquire(); doit(); } finally { mutex.release(); } This requires more programming in release() to ensure that mutex is in a consistent state: if release() is called after an exception in acquire(), the mutex may not have been acquired, or [may have been] half-acquired, etc. I should add that Joe's last point is important in the case of last month's Mutex class. The acquire_without_blocking() method, where the actual acquisition occurs, doesn't throw any exceptions at awkward times. The only exception that can be thrown from acquire(), in fact, is an InterruptedException, thrown if a timeout is specified and the waiting thread is interrupted. This operation does not leave the mutex in an unstable state, however. Page 2 of 7 Be that as it may, while looking at my code to make sure Joe hadn't found a bug, I found a bug myself. (I'll show you the code in a moment, but let's discuss the problems in it first.) The acquire() method was using a standard spin lock while waiting to acquire the mutex, but this strategy is appropriate only when the timeout is infinitely large. I've fixed the problem by
  • 43. making the loop terminate for timeout values less than Integer.MAX_VALUE (the value I use for "forever"). It continues to use a spin lock in "forever" cases. While I was at it, I also decided to have acquire() indicate whether or not it had timed out. The choices here are the usual ones: a return value or an exception toss. I opted for the latter because a timeout typically is an error condition, and I didn't want to clutter up the code with unnecessary tests for false return values. I modified the definition for the Semaphore interface to incorporate this new exception: 01 | package com.holub.asynch; 02 | 03 | interface Semaphore 04 | { 05 | int id (); 06 | void acquire(long timeout) throws InterruptedException, 07 | Timed_out; 08 | void release(); 09 | 10 | public static class Timed_out extends java.lang.RuntimeException 11 | { Timed_out(){ super("Timed out while waiting to acquire semaphore"); }; 12 | } 13 | } Note that Semaphore.Timed_out is a RuntimeException, so you don't have to catch it if the timeout is a fatal error (often the case). The new (and this time, I hope, correct) version of acquire() now looks like this: 01 | public synchronized void acquire( long timeout ) throws InterruptedException 02 | { 03 | if( timeout == 0 ) // don't wait at all 04 | { acquire_without_blocking(); 05 | } 06 | else if( timeout == Long.MAX_VALUE ) // wait forever 07 | { while( !acquire_without_blocking() ) 08 | this.wait( timeout ); 09 | } 10 | else // wait limited by timeout 11 | { if( !acquire_without_blocking() ) 12 | { this.wait( timeout ); 13 | if( !acquire_without_blocking() ) 14 | throw new Semaphore.Timed_out(); 15 | } 16 | } 17 | }
  • 44. Finally, in last month's column, I inadvertently used an outdated version of the Lock_manager's comparator class. (It threw a Not_Comparable exception -- an artifact of my own sort implementation, which I abandoned when Java added an official one.) Anyway, the comparator class should look like this: private static class Lock_comparator implements Comparator { public int compare(Object a, Object b) { return ((Semaphore)a).id() - ((Semaphore)b).id(); } public boolean equals(Object obj) { return obj instanceof Lock_comparator; } } I've modified the code in the "Goodies" section of my Web site (see Resources) to incorporate all these changes. Now, on to the meat of this month's article. Condition variables Forging ahead -- to boldly split infinitives no one has split before: I've brought up "condition variables" before in the context of wait() and notify(). The central concept is that a thread will wait until some condition becomes true. For example, a thread may need to wait for somebody to push a button before proceeding with an action, or a thread may wait for something to appear in an empty queue (for the queue-not-empty condition to become true). Page 3 of 7 Using a condition variable to wait for events The following code illustrates a classic problem that is easily solved with a condition variable: How do I wait for an event to occur without wasting machine cycles in a polling loop? The code sets up a simple TextField and an ActionListener that's notified when the user types the Enter key: 01 | class Some_class extends Frame 02 | { 03 | TextField input = new TextField(); 04 | String entered = ""; 05 | 06 | public The_wrong_way() 07 | { input.addActionListener 08 | ( new ActionListener() 09 | { public void actionPerformed( ActionEvent e ) 10 | { entered = input.getText();
  • 45. 11 | } 12 | } 13 | ); 14 | 15 | add(input); 16 | pack(); 17 | show(); 18 | } 19 | 20 | String read_line(){ return entered; } 21 | //... 22 | } So far, so good, but let's look at the situation in more detail. When you display the Frame, AWT fires up a thread that monitors events coming in from the operating system, including key-press events. When the Enter key is pressed, for example, the AWT thread gets the key-press event from the OS and, in turn, calls the listener's actionPerformed() method. The "actionPerformed()" messages are coming in asynchronously from the AWT event-loop thread. Put another way: the "actionPerformed()" message is actually running on that AWT thread. Meanwhile, a user thread (as differentiated from the AWT thread) calls read_line() to find out what the user has typed. The problem is that both the AWT and the user thread can access the entered field simultaneously -- a classic race condition. The second thread could call read_line() while the AWT thread is in the middle of ActionPerformed() and end up reading garbage. Solve this first problem with synchronization: 01 | class Some_class extends Frame 02 | { 03 | TextField input = new TextField(); 04 | String entered = ""; 05 | 06 | public The_wrong_way() 07 | { input.addActionListener 08 | ( new ActionListener() 09 | { public void actionPerformed( ActionEvent e ) 10 | { synchronized( Some_class.this ) 11 | { entered = input.getText(); 12 | } 13 | } 14 | } 15 | ); 16 | 17 | add(input); 18 | pack(); 19 | show();
  • 46. 20 | } 21 | 22 | String synchronized read_line(){ return entered; } 23 | //... 24 | } Note that the inner-class method has to synchronize explicitly on the outer-class object. Simply synchronizing actionPerformed() doesn't work because you'll be synchronizing on the monitor of the anonymous inner-class object, and the field you want to guard is in the outer-class object. Moving on, our user thread needs to know when an entire line has been typed to be sure that read_line() will return a complete line of input, but (and this is the big but), there's no direct communication between the two threads involved in this transaction. The code running on the AWT thread (actionPerformed()) doesn't tell the user thread that an entire-line-has-been-typed event has occurred. So how does the caller of read_line() know the string has changed? It could sit in a tight polling loop calling read_line() and checking the current return value against the previously returned value, but that's an awful lot of machine cycles wasted on doing nothing useful. Page 4 of 7 Send in the cavalry So what's one way for two threads to communicate with each other? (That's a rhetorical question.) Use a semaphore (think Napoleon, flags, mountain tops). To the rescue comes the semaphore known as a condition variable. To rehash from previous months' material: the basic notion of a condition variable is that some thread waits (is suspended) on the condition variable until the condition it represents becomes true. Every Java object has a condition variable associated with it -- in the same way it has the mutex used to guard the monitor. You wait for the condition to become true by calling wait(), and you set the condition to true by calling notify(). (The notify() call doesn't work in quite this way; I'll talk more about this in a moment.) It's easy enough to do a roll-your-own condition variable that solves the current thread- communication problem by using wait() and notify(). Listing 1 demonstrates how to do this. A condition variable called text_has_been_entered is declared up at the top of the class definition. (We're going to wait for the text-has-been- entered condition to become true.) The actionPerformed() method doesn't read the text at all; rather, it simply notifies the condition variable, setting the condition to true. Note that Java requires you to be in the monitor for an object before you can call wait() or notify() on that object, so the synchronized(text_has_been_entered) statement is mandatory. (You must be holding the lock associated with the object, by being in either a synchronized function of that object's class or a standalone synchronized statement whose argument is the object on which you're synchronizing.)
  • 47. The synchronized(text_has_been_entered) statement on line 15 is mandatory, since entering the synchronized block puts us into the monitor of the object referenced by text_has_been_entered. Meanwhile, down in read_line() on line 33, the thread that calls read_line() is waiting for the condition to become true. When this happens, the new text value is read and returned. The read_line() method is itself synchronized so that two threads can't attempt to read the same line simultaneously. It's now possible to have a simple loop like the one in main() while( (input = source.read_line()) != null ) System.out.println("Got: " + input ); which blocks until a new input line arrives. Listing 1: Using wait() and notify() for a condition variable 01 | import java.awt.*; 02 | import java.awt.event.*; 03 | 04 | public class Input_source extends Frame 05 | { 06 | int[] text_has_been_entered = new int[1]; // The condition variable 07 | 08 | TextField input = new TextField(); 09 | 10 | public Input_source() 11 | { 12 | input.addActionListener 13 | ( new ActionListener() 14 | { public void actionPerformed( ActionEvent e ) 15 | { synchronized( text_has_been_entered ) 16 | { text_has_been_entered.notify(); // set the condition true 17 | } 18 | } 19 | } 20 | ); 21 | 22 | add(input); 23 | pack(); 24 | show(); 25 | } 26 | 27 | /** A blocking function that works like readLine(), but gets its 28 | * text from the current window's text area. The function doesn't 29 | * return until somebody types a line of text, whereupon it returns 30 | * the line. Returns null if the user types an empty line.
  • 48. 31 | */ 32 | 33 | synchronized String read_line() throws InterruptedException 34 | { synchronized( text_has_been_entered ) 35 | { text_has_been_entered.wait(); // wait for the condition to become true 36 | } 37 | 38 | String entered = input.getText(); 39 | input.setText(""); 40 | return (entered.length() == 0) ? null : entered; 41 | 42 | } 43 | 44 | 45 | static public void main( String[] args ) throws Exception 46 | { Input_source source = new Input_source(); 47 | String input; 48 | 49 | while( (input = source.read_line()) != null ) 50 | System.out.println("Got: " + input ); 51 | 52 | System.exit(0); // kill the AWT Thread on exit 53 | } 54 | } But Wellington is coming A subtle problem occurs when using Java's built-in condition variable, however. What if you call read_line() just after the value has changed rather than before? You'll just wait until the value is changed again, missing the first value entirely. The problem is that the built-in condition variable doesn't really have a notion of state associated with it. What we really need, to solve the current problem, is a true condition variable -- one that blocks only if we try to wait() when the condition is false, and that doesn't block at all if the condition is true. Page 5 of 7 Listing 2 shows a simple implementation of such a beast. There's not much to it -- the vast majority of the file is comments. The main thing is the _is_true flag declared at the top of the class. The _is_true flag stores the state of the condition variable. You can set the condition to true or false by calling set_true() or set_false(). You can test the current state without blocking by calling is_true() or is_false(). You can block, waiting for the condition to become true by calling wait_for_true(), which doesn't block at all if the condition happens to be true when you call it. Listing 2: A condition-variable implementation 001 | package com.holub.asynch;