Core Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Core Java Technologies Tech Tips for February 16, 2005. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE). This issue covers: * Getting to Know Synchronizers * HotSpot Garbage Collection Configuration Options * Errata These tips were developed using Java 2 Platform Standard Edition Development Kit 5.0 (JDK 5.0). You can download JDK 5.0 at http://java.sun.com/j2se/1.5.0/download.jsp. This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc. (http://www.jzventures.com). You can view this issue of the Tech Tips on the Web at http://java.sun.com/developer/JDCTechTips/2005/tt0216.html See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms. For more Java technology content, visit these sites: java.sun.com - The latest Java platform releases, tutorials, and newsletters. java.net - A web forum for collaborating and building solutions together. java.com - The marketplace for Java technology, applications and services. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - GETTING TO KNOW SYNCHRONIZERS Support for synchronization has been part of the Java programming language since the initial release of the Java platform. This support is designed to prevent simultaneous access to critical code blocks and shared variables. Synchronization gives you the choice of adding the synchronized keyword to a method or wrapping a code section in a synchronized block. J2SE 5.0 adds several mechanisms for coordinating between different threads in an application. This added support is provided by the new java.util.concurrent package. The package includes classes that offer semaphores, barriers, latches, and exchangers -- this tip looks at each of these in more detail. Class Semaphore Wikipedia, ( http://en.wikipedia.org/wiki/Semaphore_(programming) ) defines a semaphore as follows: A semaphore is a protected variable (or abstract data type) and constitutes the classic method for restricting access to shared resources (e.g. storage) in a multi-processing environment. In other words, where a synchronized block allows only one entity to access a shared resource, a semaphore allows 'n' entities to access the resource. As an example, imagine going to the bank. Imagine too that there are few tellers, so that the line waiting to make a bank transaction is rather long. Think of the number of available tellers (that is, tellers who are currently not handling a customer) as the semaphore count. When the count is zero, no one new can engage with a teller. When a teller becomes available, the count increases, and another customer in the bank line can approach the teller. After the teller is engaged with a customer, the count is reduced. If no one is waiting in the bank line, the count remains above zero. Typically in a bank, the people waiting in a queue are handled in a first-in first-out (FIFO) manner. However, not all queues proceed in this fair, equitable way. For instance, sometimes people jump ahead in the line for various reasons. Before going any further, it's helpful to define three synchronization-related terms: lock, permit, and block. A lock is a construct for controlling access to a shared resource by multiple threads. Commonly, a lock provides exclusive access to a shared resource -- only one thread at a time can acquire the lock and all access to the shared resource requires that the lock be acquired first. A permit is like a ticket or token to a lock that is associated with a semaphore. A semaphore maintains a set of permits typically for a restricted pool of resources. A thread must acquire a permit from the semaphore before it can obtain a resource from the pool. When the thread finishes with the resource, it returns it back to the pool. The permit is then returned to the semaphore, allowing another thread to acquire that resource. A block signifies that a thread is waiting for a required lock or permit before it can access a shared resource. With those definitions in mind, let's look at the new Semaphore class, the class in J2SE 5.0 that offers support for semaphores. You create the class with one of the following two constructors: Semaphore(int permits) Semaphore(int permits, boolean fair) Unless specified, the created Semaphore has a "non-fair" setting. This means that FIFO behavior is not guaranteed. To prevent starvation (a thread never getting a resource), it is good practice to always create fair semaphores. However, there are throughput advantages if you ignore fairness. To acquire a permit from a semaphore, you call one of the following methods: acquire() acquire(int permits) The acquire() signature of the method gets a single permit. The acquire(int permits) signature of the method gets the requested number of permits. The methods block until a permit is available, or until the waiting thread is interrupted. If the thread is interrupted, an InterruptedException is thrown. The next two methods also acquire permits, but their threads are dormant and uninterruptible until the required number of permits are available. If a thread is interrupted while waiting, it is discovered after the resource becomes available. acquireUninterruptibly() acquireUninterruptibly(int permits) The tryAcquire methods do not block. If the required number of permits are available, the methods return true. If the permits are not available, the methods return false. All permits must be available for true to be returned. The fairness setting is ignored with these untimed tryAcquire methods. tryAcquire() tryAcquire(int permits) The timed versions work similarly to the untimed version, but wait for the specified TimeUnit before returning. If interrupted while waiting, the methods throw an InterruptedException. tryAcquire(long timeout, TimeUnit unit) tryAcquire(int permits, long timeout, TimeUnit unit) The following line shows how to wait 30 seconds for one permit: boolean acq = tryAcquire(30, TimeUnit.SECONDS) Now let's look at an example that uses the Semaphore class. The following program, SemaphoreTest, simulates a service provided by an online auction house. The service allows customers to sample multiple items available for purchase, and displays an average of the prices of these items. However due to licensing limitations, only two threads can access the service at a time. To simulate the price sampling and averaging, the SemaphoreTest program waits 50 milliseconds, and then returns a random number from 0-to-100. If the service is concurrently accessed after the limit of two threads is reached, a second pricing scheme is used. This latter scheme doesn't have the licensing restriction, but returns a less-reliable price (in this example, $20). Here is the SemaphoreTest program: import java.util.concurrent.*; import java.util.*; public class SemaphoreTest { private static final int LOOP_COUNT = 100; private static final int MAX_AVAILABLE = 2; private final static Semaphore semaphore = new Semaphore(MAX_AVAILABLE, true); private static class Pricer { private static final Random random = new Random(); public static int getGoodPrice() { int price = random.nextInt(100); try { Thread.sleep(50); } catch (InterruptedException ex) { ex.printStackTrace(); } return price; } public static int getBadPrice() { return 20; } } public static void main(String args[]) { for (int i=0; i The Exchanger is the last of the synchronization utilities covered in this tip. It offers a simplified way of communicating between threads, that is, by passing a specific object between two threads. That's why there's the after the class name. Instead of using Piped streams for stream-based, inter-thread communication (where one side writes and the other reads), the Exchanger relies on a single exchange method for the transfer of one-off data between threads. The Exchanger is not a general replacement for the piped model, but their usages are similar. The Exchanger constructor takes no arguments here, other than identifying the type of object to exchange: Exchanger> exchanger = new Exchanger>(); To exchange objects, you call the exchange method. The method transfers objects in both directions, not just one: V exchange(V x) An Exchanger is often used when you have two threads, one consuming a resource, and the other producing it. When the buffer used by the producer is full, the producer waits for the consumer. When the buffer used by the consumer is empty, the consumer waits for the producer. After both waits happen, the two threads swap buffers. This works well when you know more items will be produced. Otherwise, items will sit waiting for a full buffer before swapping buffers. Here's an example that uses the Exchanger. The FillingLoop class is the producer type. The EmptyingLoop class is the consumer. When the producer's data structure is full, it tries to exchange with the consumer. When the consumer's data structure is empty, it tries to exchange data structures with the producer. After both the producer and consumer are waiting, the exchange happens. import java.util.*; import java.util.concurrent.*; public class ExchangerTest { private static final int FULL = 10; private static final int COUNT = FULL * 20; private static final Random random = new Random(); private static volatile int sum = 0; private static Exchanger> exchanger = new Exchanger>(); private static List initiallyEmptyBuffer; private static List initiallyFullBuffer; private static CountDownLatch stopLatch = new CountDownLatch(2); private static class FillingLoop implements Runnable { public void run() { List currentBuffer = initiallyEmptyBuffer; try { for (int i = 0; i < COUNT; i++) { if (currentBuffer == null) break; // stop on null Integer item = random.nextInt(100); System.out.println("Added: " + item); currentBuffer.add(item); if (currentBuffer.size() == FULL) currentBuffer = exchanger.exchange(currentBuffer); } } catch (InterruptedException ex) { System.out.println("Bad exchange on filling side"); } stopLatch.countDown(); } } private static class EmptyingLoop implements Runnable { public void run() { List currentBuffer = initiallyFullBuffer; try { for (int i = 0; i < COUNT; i++) { if (currentBuffer == null) break; // stop on null Integer item = currentBuffer.remove(0); System.out.println("Got: " + item); sum += item.intValue(); if (currentBuffer.isEmpty()) { currentBuffer = exchanger.exchange(currentBuffer); } } } catch (InterruptedException ex) { System.out.println("Bad exchange on emptying side"); } stopLatch.countDown(); } } public static void main(String args[]) { initiallyEmptyBuffer = new ArrayList(); initiallyFullBuffer = new ArrayList(FULL); for (int i=0; i