If you've not begun to spew out syncrhonized statements by this point in the chapter, you are probably the type of programmer that would be into the new synchronizers available in the java.util.concurrent package. Unfortunately, the intimate details are beyond this book (it's a notebook, not an encyclopedia); still, here are some basics to get you pointed in the right direction.
Tiger introduces four synchronizer classes. They all allow you to force threads to wait for a specific condition before continuing execution:
NOTE
All four of these classes are in the java.util.concurrent package.
The term semaphore has actually been around as long as concurrent programming. A semaphore represents one or more permits. Threads call acquire( ) to obtain a permit from the semaphore, and release( ) when done with the permit. If no permits are available, acquire( ) blocks, waiting for one to become available. In other words, a semaphore acts like a bouncer, only allowing so many people into the party at one time.
Variants on acquire( ), which allow some control over what happens when a block is encountered, include tryAcquire( ), which either never blocks or blocks for a specified timeout, and acquireUninterruptibly( ), which won't let go even if an InterruptionException occurs.
A CountDownLatch is used to block threads until a certain set of operations is complete. When a latch is created, it is closedany thread that calls the latch's await( ) method will block until the latch is opened. This allows threads to wait on the latch, ensuring that all operations are complete before continuing.
Threads that are performing those required operations can call countDown( ) to decrement the counter supplied to a CountDownLatch at its construction. When the latch's counter reaches 0, the latch opens, and all threads sitting in await( ) become unblocked and continue execution.
An Exchanger provides for thread rendezvous for two threads, typically in a consumer-producer relationship. At some point these threads must "synch up," and possibly exchange the results of their individual tasks.
The most common use of an Exchanger is when a producer fills a buffer with data, and a consumer drains data from another source. Once the producer has filled its buffer, and the consumer drained its buffer, the two can swap buffers, and continue operation. However, both threads must complete their tasks before swapping. Exchanger.exchange( ) does the work here, as you might expect.
CyclicBarrier is another thread rendezvous facility. However, this handles the case where multiple threads (generally more than two) must all rendezvous at a specified point. You specify the number of threads when you create the barrier, and then each thread calls await( ) when it reaches the point where it's ready to rendezvous. That blocks the thread until all related threads reach the barrier.
Once all the threads have called await( ), the blocking stops, and all the threads can continue (and often interact). Additionally, the barrier is all-or-none: if one thread fails abnormally, and leaves a barrier point prematurely, all threads leave abnormally.
All of these are covered in detail in the newest edition of Java Threads (O'Reilly), which should be showing up on bookshelves in mid- to latesummer, 2004. If you're into mutexes, semaphores, and latches, that's the place to go. They'll also be covered in the upcoming Java in a Nutshell, Fifth Edition (O'Reilly).