/* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain. Use, modify, and * redistribute this code in any way without acknowledgement. */ package java.util.concurrent; /** * An implementation of {@link ReadWriteLock} supporting similar * semantics to {@link ReentrantLock}. *

This class has the following properties: *

* *

Sample usage. Here is a code sketch showing how to exploit * reentrancy to perform lock downgrading after updating a cache (exception * handling is elided for simplicity): *

 * class CachedData {
 *   Object data;
 *   volatile boolean cacheValid;
 *   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 *
 *   void processCachedData() {
 *     rwl.readLock().lock();
 *     if (!cacheValid) {
 *        // upgrade lock manually
 *        rwl.readLock().unlock();   // must unlock first to obtain writelock
 *        rwl.writeLock().lock();
 *        if (!cacheValid) { // recheck
 *          data = ...
 *          cacheValid = true;
 *        }
 *        // downgrade lock
 *        rwl.readLock().lock();  // reacquire read without giving up write lock
 *        rwl.writeLock().unlock(); // unlock write, still hold read
 *     }
 *
 *     use(data);
 *     rwl.readLock().unlock();
 *   }
 * }
 * 
* * @see ReentrantLock * @see Condition * @see ReadWriteLock * @see Locks * * @since 1.5 * @spec JSR-166 * @revised $Date: 2003/07/08 00:46:35 $ * @editor $Author: dl $ * @author Doug Lea * */ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { /* Note that all fields are transient and defined in a way that deserialized locks are in initial unlocked state. */ /** Inner class providing readlock */ private final Lock readerLock = new ReaderLock(); /** Inner class providing writelock */ private final Lock writerLock = new WriterLock(); public Lock writeLock() { return writerLock; } public Lock readLock() { return readerLock; } /** * Main lock. Writers acquire on entry, and hold until release. * Reentrant acquires on write lock are allowed. Readers acquire * and release only during entry, but are blocked from doing so if * there is an active writer. RRWLock is a simple ReentrantLock * subclass defined below that surrounds WriteLock condition waits * with bookeeping to save and restore writer state. **/ private final RRWLock entryLock = new RRWLock(); /** * Number of threads that have entered read lock. This is never * reset to zero. Incremented only during acquisition of read lock * while the "entryLock" is held, but read elsewhere, so is declared * volatile. **/ private transient volatile int readers; /** * Number of threads that have exited read lock. This is never * reset to zero. Accessed only in code protected by rLock. When * exreaders != readers, the rwlock is being used for * reading. Else if the entry lock is held, it is being used for * writing (or in transition). Else it is free. Note: To * distinguish these states, we assume that fewer than 2^32 reader * threads can simultaneously execute. **/ private transient int exreaders; /** * Flag set while writing. Needed by ReaderLock.tryLock to * distinguish contention from unavailability. Accessed * in ways that do not require being volatile. */ private transient boolean writing; /** * Lock used by exiting readers and entering writers */ private final ReentrantLock writeCheckLock = new ReentrantLock(); /** * Condition waited on by blocked entering writers. */ private final Condition writeEnabled = writeCheckLock.newCondition(); /** * Reader exit protocol, called from unlock. if this is the last * reader, notify a possibly waiting writer. Because waits occur * only when entry lock is held, at most one writer can be waiting * for this notification. Because increments to "readers" aren't * protected by "this" lock, the notification may be spurious * (when an incoming reader in in the process of updating the * field), but at the point tested in acquiring write lock, both * locks will be held, thus avoiding false alarms. And we will * never miss an opportunity to send a notification when it is * actually needed. */ private void readerExit() { writeCheckLock.lock(); try { if (++exreaders == readers) writeEnabled.signal(); } finally { writeCheckLock.unlock(); } } /** * Writer enter protocol, called after acquiring entry lock. * Wait until all readers have exited. * @throws InterruptedException if interrupted while waiting */ private void writerEnter() throws InterruptedException { writeCheckLock.lock(); try { while (exreaders != readers) writeEnabled.await(); } finally { writeCheckLock.unlock(); } } /** * Trylock version of Writer enter protocol * @return true if successful */ private boolean tryWriterEnter() { writeCheckLock.lock(); boolean ok = (exreaders == readers); writeCheckLock.unlock(); return ok; } /** * Timed version of writer enter protocol, called after acquiring * entry lock. Wait until all readers have exited. * @param nanos the wait time * @return true if successful * @throws InterruptedException if interrupted while waiting */ private boolean tryWriterEnter(long nanos) throws InterruptedException { writeCheckLock.lock(); try { while (exreaders != readers) { if (nanos <= 0) return false; nanos = writeEnabled.awaitNanos(nanos); } return true; } finally { writeCheckLock.unlock(); } } /** * The Reader lock */ private class ReaderLock implements Lock { public void lock() { entryLock.lock(); ++readers; entryLock.unlock(); } public void lockInterruptibly() throws InterruptedException { entryLock.lockInterruptibly(); ++readers; entryLock.unlock(); } public boolean tryLock() { // if we fail entry lock just due to contention, try again. while (!entryLock.tryLock()) { if (writing) return false; else Thread.yield(); } ++readers; entryLock.unlock(); return true; } public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { if (!entryLock.tryLock(time, unit)) return false; ++readers; entryLock.unlock(); return true; } public void unlock() { readerExit(); } public Condition newCondition() { throw new UnsupportedOperationException(); } } /** * The writer lock */ private class WriterLock implements Lock { public void lock() { entryLock.lock(); writing = true; if (entryLock.getHoldCount() > 1) // skip if held reentrantly return; boolean wasInterrupted = false; for (;;) { try { writerEnter(); break; } catch (InterruptedException ie) { wasInterrupted = true; } } if (wasInterrupted) { Thread.currentThread().interrupt(); } } public void lockInterruptibly() throws InterruptedException { entryLock.lockInterruptibly(); writing = true; if (entryLock.getHoldCount() > 1) // skip if held reentrantly return; try { writerEnter(); } catch (InterruptedException ie) { writing = false; entryLock.unlock(); throw ie; } } public boolean tryLock( ) { if (!entryLock.tryLock()) return false; writing = true; if (entryLock.getHoldCount() > 1) return true; if (!tryWriterEnter()) { entryLock.unlock(); writing = false; return false; } return true; } public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { long startTime = JSR166Support.currentTimeNanos(); long nanos = unit.toNanos(time); if (!entryLock.tryLock(nanos, TimeUnit.NANOSECONDS)) return false; writing = true; if (entryLock.getHoldCount() > 1) return true; nanos -= JSR166Support.currentTimeNanos() - startTime; try { if (!tryWriterEnter(nanos)) { entryLock.unlock(); writing = false; return false; } } catch (InterruptedException ie) { writing = false; entryLock.unlock(); throw ie; } return true; } public void unlock() { // must perform owner check before clearing "writing" if (!entryLock.isHeldByCurrentThread()) throw new IllegalMonitorStateException(); writing = false; entryLock.unlock(); } public Condition newCondition() { return entryLock.newCondition(); } } /** * Subclass of ReentrantLock to adjust fields on entry and exit to * condition waits. */ class RRWLock extends FairReentrantLock { void beforeWait() { writing = false; } void afterWait() { writing = true; // same as lock, except never bypass writerEnter. boolean wasInterrupted = false; for (;;) { try { writerEnter(); break; } catch (InterruptedException ie) { wasInterrupted = true; } } if (wasInterrupted) { Thread.currentThread().interrupt(); } } } }