package com.cosylab.acs.maci.manager; /** * A ReadWriteLock that prefers waiting writers over waiting readers when there * is contention. This class is adapted from the versions described in CPJ, * improving on the ones there a bit by segregating reader and writer wait * queues, which is typically more efficient. * <p> * The locks are <em>NOT</em> reentrant. In particular, even though it may * appear to usually work OK, a thread holding a read lock should not attempt to * re-acquire it. Doing so risks lockouts when there are also waiting writers. * <p>[<a * href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> * Introduction to this package. </a>] */ public class ReaderPreferenceReadWriteLock { protected long activeReaders_ = 0; protected Thread activeWriter_ = null; protected long waitingReaders_ = 0; protected long waitingWriters_ = 0; protected final ReaderLock readerLock_ = new ReaderLock(); protected final WriterLock writerLock_ = new WriterLock(); public Sync writeLock() { return writerLock_; } public Sync readLock() { return readerLock_; } /* * A bunch of small synchronized methods are needed to allow communication * from the Lock objects back to this object, that serves as controller */ protected synchronized void cancelledWaitingReader() { --waitingReaders_; } protected synchronized void cancelledWaitingWriter() { --waitingWriters_; } protected boolean allowReader() { return activeWriter_ == null; // writer preference code: return activeWriter_ == null && // waitingWriters_ == 0; } protected synchronized boolean startRead() { boolean allowRead = allowReader(); if (allowRead) ++activeReaders_; return allowRead; } protected synchronized boolean startWrite() { // The allowWrite expression cannot be modified without // also changing startWrite, so is hard-wired boolean allowWrite = (activeWriter_ == null && activeReaders_ == 0); if (allowWrite) activeWriter_ = Thread.currentThread(); return allowWrite; } /* * Each of these variants is needed to maintain atomicity of wait counts * during wait loops. They could be made faster by manually inlining each * other. We hope that compilers do this for us though. */ protected synchronized boolean startReadFromNewReader() { boolean pass = startRead(); if (!pass) ++waitingReaders_; return pass; } protected synchronized boolean startWriteFromNewWriter() { boolean pass = startWrite(); if (!pass) ++waitingWriters_; return pass; } protected synchronized boolean startReadFromWaitingReader() { boolean pass = startRead(); if (pass) --waitingReaders_; return pass; } protected synchronized boolean startWriteFromWaitingWriter() { boolean pass = startWrite(); if (pass) --waitingWriters_; return pass; } /** * Called upon termination of a read. Returns the object to signal to wake * up a waiter, or null if no such */ protected synchronized Signaller endRead() { if (--activeReaders_ == 0 && waitingWriters_ > 0) return writerLock_; else return null; } /** * Called upon termination of a write. Returns the object to signal to wake * up a waiter, or null if no such */ protected synchronized Signaller endWrite() { activeWriter_ = null; if (waitingReaders_ > 0 && allowReader()) return readerLock_; else if (waitingWriters_ > 0) return writerLock_; else return null; } /** * Reader and Writer requests are maintained in two different wait sets, by * two different objects. These objects do not know whether the wait sets * need notification since they don't know preference rules. So, each * supports a method that can be selected by main controlling object to * perform the notifications. This base class simplifies mechanics. */ protected abstract class Signaller { // base for ReaderLock and // WriterLock abstract void signalWaiters(); } /** * Main interface for locks, gates, and conditions. * <p> * Sync objects isolate waiting and notification for particular logical * states, resource availability, events, and the like that are shared * across multiple threads. Use of Syncs sometimes (but by no means always) * adds flexibility and efficiency compared to the use of plain java monitor * methods and locking, and are sometimes (but by no means always) simpler * to program with. * <p> * * Most Syncs are intended to be used primarily (although not exclusively) * in before/after constructions such as: * * <pre> * class X { * Sync gate; * * // ... * * public void m() { * try { * gate.acquire(); // block until condition holds * try { * // ... method body * } * finally { * gate.release() * } * } * catch (InterruptedException ex) { * // ... evasive action * } * } * * public void m2(Sync cond) { // use supplied condition * try { * if (cond.attempt(10)) { // try the condition for 10 ms * try { * // ... method body * } * finally { * cond.release() * } * } * } * catch (InterruptedException ex) { * // ... evasive action * } * } * } * </pre> * * Syncs may be used in somewhat tedious but more flexible replacements for * built-in Java synchronized blocks. For example: * * <pre> * class HandSynched { * private double state_ = 0.0; * * private final Sync lock; // use lock type supplied in constructor * * public HandSynched(Sync l) { * lock = l; * } * * public void changeState(double d) { * try { * lock.acquire(); * try { * state_ = updateFunction(d); * } finally { * lock.release(); * } * } catch (InterruptedException ex) { * } * } * * public double getState() { * double d = 0.0; * try { * lock.acquire(); * try { * d = accessFunction(state_); * } finally { * lock.release(); * } * } catch (InterruptedException ex) { * } * return d; * } * * private double updateFunction(double d) { ... } * * private double accessFunction(double d) { ... } * } * </pre> * * If you have a lot of such methods, and they take a common form, you can * standardize this using wrappers. Some of these wrappers are standardized * in LockedExecutor, but you can make others. For example: * * <pre> * class HandSynchedV2 { * private double state_ = 0.0; * * private final Sync lock; // use lock type supplied in constructor * * public HandSynchedV2(Sync l) { * lock = l; * } * * protected void runSafely(Runnable r) { * try { * lock.acquire(); * try { * r.run(); * } finally { * lock.release(); * } * } catch (InterruptedException ex) { // propagate without throwing * Thread.currentThread().interrupt(); * } * } * * public void changeState(double d) { * runSafely(new Runnable() { * public void run() { * state_ = updateFunction(d); * } * }); * } * // ... * } * </pre> * * <p> * One reason to bother with such constructions is to use deadlock- avoiding * back-offs when dealing with locks involving multiple objects. For * example, here is a Cell class that uses attempt to back-off and retry if * two Cells are trying to swap values with each other at the same time. * * <pre> * class Cell { * long value; * Sync lock = ... // some sync implementation class * void swapValue(Cell other) { * for (;;) { * try { * lock.acquire(); * try { * if (other.lock.attempt(100)) { * try { * long t = value; * value = other.value; * other.value = t; * return; * } * finally { other.lock.release(); } * } * } * finally { lock.release(); } * } * catch (InterruptedException ex) { return; } * } * } * } * </pre> * * <p> * Here is an even fancier version, that uses lock re-ordering upon * conflict: * * <pre> * class Cell { * long value; * Sync lock = ...; * private static boolean trySwap(Cell a, Cell b) { * a.lock.acquire(); * try { * if (!b.lock.attempt(0)) * return false; * try { * long t = a.value; * a.value = b.value; * b.value = t; * return true; * } * finally { other.lock.release(); } * } * finally { lock.release(); } * return false; * } * * void swapValue(Cell other) { * try { * while (!trySwap(this, other) && * !tryswap(other, this)) * Thread.sleep(1); * } * catch (InterruptedException ex) { return; } * } * } * </pre> * * <p> * Interruptions are in general handled as early as possible. Normally, * InterruptionExceptions are thrown in acquire and attempt(msec) if * interruption is detected upon entry to the method, as well as in any * later context surrounding waits. However, interruption status is ignored * in release(); * <p> * Timed versions of attempt report failure via return value. If so desired, * you can transform such constructions to use exception throws via * * <pre> * if (!c.attempt(timeval)) * throw new TimeoutException(timeval); * </pre> * * <p> * The TimoutSync wrapper class can be used to automate such usages. * <p> * All time values are expressed in milliseconds as longs, which have a * maximum value of Long.MAX_VALUE, or almost 300,000 centuries. It is not * known whether JVMs actually deal correctly with such extreme values. For * convenience, some useful time values are defined as static constants. * <p> * All implementations of the three Sync methods guarantee to somehow employ * Java <code>synchronized</code> methods or blocks, and so entail the * memory operations described in JLS chapter 17 which ensure that variables * are loaded and flushed within before/after constructions. * <p> * Syncs may also be used in spinlock constructions. Although it is normally * best to just use acquire(), various forms of busy waits can be * implemented. For a simple example (but one that would probably never be * preferable to using acquire()): * * <pre> * class X { * Sync lock = ... * void spinUntilAcquired() throws InterruptedException { * // Two phase. * // First spin without pausing. * int purespins = 10; * for (int i = 0; i < purespins; ++i) { * if (lock.attempt(0)) * return true; * } * // Second phase - use timed waits * long waitTime = 1; // 1 millisecond * for (;;) { * if (lock.attempt(waitTime)) * return true; * else * waitTime = waitTime * 3 / 2 + 1; // increase 50% * } * } * } * </pre> * * <p> * In addition pure synchronization control, Syncs may be useful in any * context requiring before/after methods. For example, you can use an * ObservableSync (perhaps as part of a LayeredSync) in order to obtain * callbacks before and after each method invocation for a given class. * <p> * * <p>[<a * href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> * Introduction to this package. </a>] */ public interface Sync { /** * Wait (possibly forever) until successful passage. Fail only upon * interuption. Interruptions always result in `clean' failures. On * failure, you can be sure that it has not been acquired, and that no * corresponding release should be performed. Conversely, a normal * return guarantees that the acquire was successful. */ public void lock(); /** * Wait at most msecs to pass; report whether passed. * <p> * The method has best-effort semantics: The msecs bound cannot be * guaranteed to be a precise upper bound on wait time in Java. * Implementations generally can only attempt to return as soon as * possible after the specified bound. Also, timers in Java do not stop * during garbage collection, so timeouts can occur just because a GC * intervened. So, msecs arguments should be used in a coarse-grained * manner. Further, implementations cannot always guarantee that this * method will return at all without blocking indefinitely when used in * unintended ways. For example, deadlocks may be encountered when * called in an unintended context. * <p> * * @param msecs * the number of milleseconds to wait. An argument less than * or equal to zero means not to wait at all. However, this * may still require access to a synchronization lock, which * can impose unbounded delay if there is a lot of contention * among threads. * @return true if acquired */ public boolean attempt(long msecs) throws InterruptedException; /** * Potentially enable others to pass. * <p> * Because release does not raise exceptions, it can be used in * `finally' clauses without requiring extra embedded try/catch blocks. * But keep in mind that as with any java method, implementations may * still throw unchecked exceptions such as Error or * NullPointerException when faced with uncontinuable errors. However, * these should normally only be caught by higher-level error handlers. */ public void unlock(); /** One second, in milliseconds; convenient as a time-out value * */ public static final long ONE_SECOND = 1000; /** One minute, in milliseconds; convenient as a time-out value * */ public static final long ONE_MINUTE = 60 * ONE_SECOND; /** One hour, in milliseconds; convenient as a time-out value * */ public static final long ONE_HOUR = 60 * ONE_MINUTE; /** One day, in milliseconds; convenient as a time-out value * */ public static final long ONE_DAY = 24 * ONE_HOUR; /** One week, in milliseconds; convenient as a time-out value * */ public static final long ONE_WEEK = 7 * ONE_DAY; /** One year in milliseconds; convenient as a time-out value * */ // Not that it matters, but there is some variation across // standard sources about value at msec precision. // The value used is the same as in java.util.GregorianCalendar public static final long ONE_YEAR = (long) (365.2425 * ONE_DAY); /** One century in milliseconds; convenient as a time-out value * */ public static final long ONE_CENTURY = 100 * ONE_YEAR; } protected class ReaderLock extends Signaller implements Sync { public void lock() { InterruptedException ie = null; synchronized (this) { if (!startReadFromNewReader()) { for (;;) { try { ReaderLock.this.wait(); if (startReadFromWaitingReader()) return; } catch (InterruptedException ex) { cancelledWaitingReader(); ie = ex; break; } } } } if (ie != null) { // fall through outside synch on interrupt. // This notification is not really needed here, // but may be in plausible subclasses writerLock_.signalWaiters(); } } public void unlock() { Signaller s = endRead(); if (s != null) s.signalWaiters(); } synchronized void signalWaiters() { ReaderLock.this.notifyAll(); } public boolean attempt(long msecs) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); InterruptedException ie = null; synchronized (this) { if (msecs <= 0) return startRead(); else if (startReadFromNewReader()) return true; else { long waitTime = msecs; long start = System.currentTimeMillis(); for (;;) { try { ReaderLock.this.wait(waitTime); } catch (InterruptedException ex) { cancelledWaitingReader(); ie = ex; break; } if (startReadFromWaitingReader()) return true; else { waitTime = msecs - (System.currentTimeMillis() - start); if (waitTime <= 0) { cancelledWaitingReader(); break; } } } } } // safeguard on interrupt or timeout: writerLock_.signalWaiters(); if (ie != null) throw ie; else return false; // timed out } } protected class WriterLock extends Signaller implements Sync { public void lock() { InterruptedException ie = null; synchronized (this) { if (!startWriteFromNewWriter()) { for (;;) { try { WriterLock.this.wait(); if (startWriteFromWaitingWriter()) return; } catch (InterruptedException ex) { cancelledWaitingWriter(); WriterLock.this.notify(); ie = ex; break; } } } } if (ie != null) { // Fall through outside synch on interrupt. // On exception, we may need to signal readers. // It is not worth checking here whether it is strictly // necessary. readerLock_.signalWaiters(); } } public void unlock() { Signaller s = endWrite(); if (s != null) s.signalWaiters(); } synchronized void signalWaiters() { WriterLock.this.notify(); } public boolean attempt(long msecs) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); InterruptedException ie = null; synchronized (this) { if (msecs <= 0) return startWrite(); else if (startWriteFromNewWriter()) return true; else { long waitTime = msecs; long start = System.currentTimeMillis(); for (;;) { try { WriterLock.this.wait(waitTime); } catch (InterruptedException ex) { cancelledWaitingWriter(); WriterLock.this.notify(); ie = ex; break; } if (startWriteFromWaitingWriter()) return true; else { waitTime = msecs - (System.currentTimeMillis() - start); if (waitTime <= 0) { cancelledWaitingWriter(); WriterLock.this.notify(); break; } } } } } readerLock_.signalWaiters(); if (ie != null) throw ie; else return false; // timed out } } }