Java Cookbook, Second Edition
Problem
You want an easier means of synchronizing threads. Solution
Use the Lock mechanism, new in 1.5. Discussion
JDK 1.5 introduced the java.util.concurrent.locks package; its major interface is Lock. This interface has several methods for locking and one for unlocking. The general pattern for using it is: Lock thelock = .... try { lock.lock( ); // do the work that is protected by the lock } finally { lock.unlock( ); } The point of putting the unlock( ) call in the finally block is, of course, to ensure that it is not bypassed if an exception occurs (the code may also include one or more catch blocks, as required by the work being performed). The improvement here, compared with the traditional synchronized methods and blocks, is that using a Lock actually looks like a locking operation! And, as I mentioned, several means of locking are available, shown in Table 24-1.
The TimeUnit class lets you specify the units for the amount of time specified, including: TimeUnit.SECONDS, TimeUnit.MILLISECONDS, TimeUnit.MICROSECONDS, and TimeUnit.NANOSECONDS. In all cases the lock must be released with unlock( ) before it can be locked again. The standard Lock is useful in many applications, but depending on the application's requirements, other types of locks may be more appropriate. Applications with asymmetric load patterns may benefit from a common pattern called the "reader-writer lock"; I call this one a Readers-Writer lock to emphasize that there can be many readers but only one writer. It's actually a pair of interconnected locks; any number of readers can hold the read lock and read the data, as long as it's not being written (shared read access). A thread trying to lock the write lock, however, waits until all the readers are finished, then locks them out until the writer is finished (exclusive write access). To support this pattern, JDK 1.5 provides the ReadWriteLock interface and the implementing class ReentrantReadWriteLock. The interface has only two methods, readLock( ) and writeLock( ), which provide a reference to the appropriate Lock implementation. These methods do not, in themselves, lock or unlock the locks; they only provide access to them, so it is common to see code like: rwlock.readLock( ).lock( ); ... rwlock.readLock( ).unlock( ); To demonstrate ReadWriteLock in action, I wrote the business logic portion of a web-based voting application. It could be used in voting for candidates or for the more common web poll. Presuming that you display the results on the home page and change the data only when somebody takes the time to click on a response to vote, this application fits one of the intended criteria for ReadWriteLock i.e., that you have more readers than writers. The main class, ReadersWritersDemo, is shown in Example 24-10. The helper class BallotBox is online; it simply keeps track of the votes and returns an invariant Iterator upon request. Note that in the run( ) method of the reading threads, I obtain the iterator while holding the lock but release the lock before printing it; this allows greater concurrency and better performance. Example 24-10. ReadersWriterDemo.java
import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Simulate multiple readers */ public class ReadersWriterDemo { private static final int NUM_READER_THREADS = 3; public static void main(String[] args) { new ReadersWriterDemo( ).demo( ); } /** Set this to true to end the program */ private boolean done = false; /** The data being protected. */ private BallotBox theData; /** The read lock / write lock combination */ private ReentrantReadWriteLock lock = new ReentrantReadWriteLock( ); /** * Constructor: set up some quasi-random initial data */ public ReadersWriterDemo( ) { List questionsList = new ArrayList( ); questionsList.add("Agree"); questionsList.add("Disagree"); theData = new BallotBox(questionsList); } /** * Run a demo with more readers than writers */ private void demo( ) { // Start two reader threads for (int i = 0; i < NUM_READER_THREADS; i++) { new Thread( ) { public void run( ) { while(!done) { Iterator results = null; try { lock.readLock( ).lock( ); results = theData.iterator( ); } finally { // Unlock in finally to be sure. lock.readLock( ).unlock( ); } // Now lock has been freed, take time to print print(results); try { Thread.sleep(((long)(Math.random( )* 1000))); } catch (InterruptedException ex) { // nothing to do } } } }.start( ); } // Start one writer thread to simulate occasional voting new Thread( ) { public void run( ) { while(!done) { try { lock.writeLock( ).lock( ); theData.voteFor( (((int)(Math.random( )* theData.getCandidateCount( ))))); } finally { lock.writeLock( ).unlock( ); } try { Thread.sleep(((long)(Math.random( )*1500))); } catch (InterruptedException ex) { // nothing to do } } } }.start( ); // In the main thread, wait a while then terminate the run. try { Thread.sleep(10 *1000); } catch (InterruptedException ex) { // nothing to do } finally { done = true; } } /** print the current totals */ private void print(Iterator iter) { boolean first = true; while (iter.hasNext( )) { BallotPosition pair = (BallotPosition) iter.next( ); if (!first) System.out.print(", "); System.out.print(pair.getName( ) + "(" + pair.getVotes( ) + ")"); first = false; } System.out.println( ); } } Since this is a simulation and the voting is random, it does not always come out 50-50. In two consecutive runs, the following were the last line of each run: Agree(6), Disagree(6) Agree(9), Disagree(4) See Also
The Lock interface also makes available Condition objects, which provide even more flexibility. Consult the documentation for the 1.5 release for more information. |