/** ******************************************************************************* * Copyright (C) 2001-2006, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.impl; // See Allan Holub's 1999 column in JavaWorld, and Doug Lea's code for RWLocks with writer preference. /** *

A simple Reader/Writer lock. This assumes that there will * be little writing contention. It also doesn't allow * active readers to acquire and release a write lock, or * deal with priority inversion issues.

* *

Access to the lock should be enclosed in a try/finally block * in order to ensure that the lock is always released in case of * exceptions:

 * try {
 *     lock.acquireRead();
 *     // use service protected by the lock
 * }
 * finally {
 *     lock.releaseRead();
 * }
 * 

* *

The lock provides utility methods getStats and clearStats * to return statistics on the use of the lock.

*/ public class ICURWLock { private Object writeLock = new Object(); private Object readLock = new Object(); private int wwc; // waiting writers private int rc; // active readers, -1 if there's an active writer private int wrc; // waiting readers private Stats stats = new Stats(); // maybe don't init to start... /** * Internal class used to gather statistics on the RWLock. */ public final static class Stats { /** * Number of times read access granted (read count). */ public int _rc; /** * Number of times concurrent read access granted (multiple read count). */ public int _mrc; /** * Number of times blocked for read (waiting reader count). */ public int _wrc; // wait for read /** * Number of times write access granted (writer count). */ public int _wc; /** * Number of times blocked for write (waiting writer count). */ public int _wwc; private Stats() { } private Stats(int rc, int mrc, int wrc, int wc, int wwc) { this._rc = rc; this._mrc = mrc; this._wrc = wrc; this._wc = wc; this._wwc = wwc; } private Stats(Stats rhs) { this(rhs._rc, rhs._mrc, rhs._wrc, rhs._wc, rhs._wwc); } /** * Return a string listing all the stats. */ public String toString() { return " rc: " + _rc + " mrc: " + _mrc + " wrc: " + _wrc + " wc: " + _wc + " wwc: " + _wwc; } } /** * Reset the stats. Returns existing stats, if any. */ public synchronized Stats resetStats() { Stats result = stats; stats = new Stats(); return result; } /** * Clear the stats (stop collecting stats). Returns existing stats, if any. */ public synchronized Stats clearStats() { Stats result = stats; stats = null; return result; } /** * Return a snapshot of the current stats. This does not reset the stats. */ public synchronized Stats getStats() { return stats == null ? null : new Stats(stats); } // utilities private synchronized boolean gotRead() { ++rc; if (stats != null) { ++stats._rc; if (rc > 1) ++stats._mrc; } return true; } private synchronized boolean getRead() { if (rc >= 0 && wwc == 0) { return gotRead(); } ++wrc; return false; } private synchronized boolean retryRead() { if (stats != null) ++stats._wrc; if (rc >= 0 && wwc == 0) { --wrc; return gotRead(); } return false; } private synchronized boolean finishRead() { if (rc > 0) { return (0 == --rc && wwc > 0); } throw new IllegalStateException("no current reader to release"); } private synchronized boolean gotWrite() { rc = -1; if (stats != null) { ++stats._wc; } return true; } private synchronized boolean getWrite() { if (rc == 0) { return gotWrite(); } ++wwc; return false; } private synchronized boolean retryWrite() { if (stats != null) ++stats._wwc; if (rc == 0) { --wwc; return gotWrite(); } return false; } private static final int NOTIFY_NONE = 0; private static final int NOTIFY_WRITERS = 1; private static final int NOTIFY_READERS = 2; private synchronized int finishWrite() { if (rc < 0) { rc = 0; if (wwc > 0) { return NOTIFY_WRITERS; } else if (wrc > 0) { return NOTIFY_READERS; } else { return NOTIFY_NONE; } } throw new IllegalStateException("no current writer to release"); } /** *

Acquire a read lock, blocking until a read lock is * available. Multiple readers can concurrently hold the read * lock.

* *

If there's a writer, or a waiting writer, increment the * waiting reader count and block on this. Otherwise * increment the active reader count and return. Caller must call * releaseRead when done (for example, in a finally block).

*/ public void acquireRead() { if (!getRead()) { for (;;) { try { synchronized (readLock) { readLock.wait(); } if (retryRead()) { return; } } catch (InterruptedException e) { } } } } /** *

Release a read lock and return. An error will be thrown * if a read lock is not currently held.

* *

If this is the last active reader, notify the oldest * waiting writer. Call when finished with work * controlled by acquireRead.

*/ public void releaseRead() { if (finishRead()) { synchronized (writeLock) { writeLock.notify(); } } } /** *

Acquire the write lock, blocking until the write lock is * available. Only one writer can acquire the write lock, and * when held, no readers can acquire the read lock.

* *

If there are no readers and no waiting writers, mark as * having an active writer and return. Otherwise, add a lock to the * end of the waiting writer list, and block on it. Caller * must call releaseWrite when done (for example, in a finally * block).

*/ public void acquireWrite() { if (!getWrite()) { for (;;) { try { synchronized (writeLock) { writeLock.wait(); } if (retryWrite()) { return; } } catch (InterruptedException e) { } } } } /** *

Release the write lock and return. An error will be thrown * if the write lock is not currently held.

* *

If there are waiting readers, make them all active and * notify all of them. Otherwise, notify the oldest waiting * writer, if any. Call when finished with work controlled by * acquireWrite.

*/ public void releaseWrite() { switch (finishWrite()) { case NOTIFY_WRITERS: synchronized (writeLock) { writeLock.notify(); } break; case NOTIFY_READERS: synchronized (readLock) { readLock.notifyAll(); } break; case NOTIFY_NONE: break; } } }