All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jsr166e.extra.SequenceLock Maven / Gradle / Ivy

There is a newer version: 8.15.1
Show newest version
/*
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

package jsr166e.extra;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;
import java.util.Collection;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

/**
 * A reentrant mutual exclusion {@link Lock} in which each lock
 * acquisition or release advances a sequence number.  When the
 * sequence number (accessible using {@link #getSequence()}) is odd,
 * the lock is held. When it is even (i.e., ({@code lock.getSequence()
 * & 1L) == 0L}), the lock is released. Method {@link
 * #awaitAvailability} can be used to await availability of the lock,
 * returning its current sequence number. Sequence numbers (as well as
 * reentrant hold counts) are of type {@code long} to ensure that they
 * will not wrap around until hundreds of years of use under current
 * processor rates.  A SequenceLock can be created with a specified
 * number of spins. Attempts to acquire the lock in method {@link
 * #lock} will retry at least the given number of times before
 * blocking. If not specified, a default, possibly platform-specific,
 * value is used.
 *
 * 

Except for the lack of support for specified fairness policies, * or {@link Condition} objects, a SequenceLock can be used in the * same way as {@link ReentrantLock}. It provides similar status and * monitoring methods, such as {@link #isHeldByCurrentThread}. * SequenceLocks may be preferable in contexts in which multiple * threads invoke short read-only methods much more frequently than * fully locked methods. * *

Methods {@code awaitAvailability} and {@code getSequence} can * be used together to define (partially) optimistic read-only methods * that are usually more efficient than ReadWriteLocks when they * apply. These methods should in general be structured as loops that * await lock availability, then read {@code volatile} fields into * local variables (and may further read other values derived from * these, for example the {@code length} of a {@code volatile} array), * and retry if the sequence number changed while doing so. * Alternatively, because {@code awaitAvailability} accommodates * reentrancy, a method can retry a bounded number of times before * switching to locking mode. While conceptually straightforward, * expressing these ideas can be verbose. For example: * *

 {@code
 * class Point {
 *   private volatile double x, y;
 *   private final SequenceLock sl = new SequenceLock();
 *
 *   // an exclusively locked method
 *   void move(double deltaX, double deltaY) {
 *     sl.lock();
 *     try {
 *       x += deltaX;
 *       y += deltaY;
 *     } finally {
 *       sl.unlock();
 *     }
 *   }
 *
 *   // A read-only method
 *   double distanceFromOriginV1() {
 *     double currentX, currentY;
 *     long seq;
 *     do {
 *       seq = sl.awaitAvailability();
 *       currentX = x;
 *       currentY = y;
 *     } while (sl.getSequence() != seq); // retry if sequence changed
 *     return Math.sqrt(currentX * currentX + currentY * currentY);
 *   }
 *
 *   // Uses bounded retries before locking
 *   double distanceFromOriginV2() {
 *     double currentX, currentY;
 *     long seq;
 *     int retries = RETRIES_BEFORE_LOCKING; // for example 8
 *     try {
 *       do {
 *         if (--retries < 0)
 *           sl.lock();
 *         seq = sl.awaitAvailability();
 *         currentX = x;
 *         currentY = y;
 *       } while (sl.getSequence() != seq);
 *     } finally {
 *       if (retries < 0)
 *         sl.unlock();
 *     }
 *     return Math.sqrt(currentX * currentX + currentY * currentY);
 *   }
 * }}
* * @since 1.8 * @author Doug Lea */ public class SequenceLock implements Lock, java.io.Serializable { private static final long serialVersionUID = 7373984872572414699L; static final class Sync extends AbstractQueuedLongSynchronizer { private static final long serialVersionUID = 2540673546047039555L; /** * The number of times to spin in lock() and awaitAvailability(). */ final int spins; /** * The number of reentrant holds on this lock. Uses a long for * compatibility with other AbstractQueuedLongSynchronizer * operations. Accessed only by lock holder. */ long holds; Sync(int spins) { this.spins = spins; } // overrides of AQLS methods public final boolean isHeldExclusively() { return (getState() & 1L) != 0L && getExclusiveOwnerThread() == Thread.currentThread(); } public final boolean tryAcquire(long acquires) { Thread current = Thread.currentThread(); long c = getState(); if ((c & 1L) == 0L) { if (compareAndSetState(c, c + 1L)) { holds = acquires; setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { holds += acquires; return true; } return false; } public final boolean tryRelease(long releases) { if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); if ((holds -= releases) == 0L) { setExclusiveOwnerThread(null); setState(getState() + 1L); return true; } return false; } public final long tryAcquireShared(long unused) { return (((getState() & 1L) == 0L) ? 1L : (getExclusiveOwnerThread() == Thread.currentThread()) ? 0L: -1L); } public final boolean tryReleaseShared(long unused) { return (getState() & 1L) == 0L; } public final Condition newCondition() { throw new UnsupportedOperationException(); } // Other methods in support of SequenceLock final long getSequence() { return getState(); } final void lock() { int k = spins; while (!tryAcquire(1L)) { if (k == 0) { acquire(1L); break; } --k; } } final long awaitAvailability() { long s; while (((s = getState()) & 1L) != 0L && getExclusiveOwnerThread() != Thread.currentThread()) { acquireShared(1L); releaseShared(1L); } return s; } final long tryAwaitAvailability(long nanos) throws InterruptedException, TimeoutException { Thread current = Thread.currentThread(); for (;;) { long s = getState(); if ((s & 1L) == 0L || getExclusiveOwnerThread() == current) { releaseShared(1L); return s; } if (!tryAcquireSharedNanos(1L, nanos)) throw new TimeoutException(); // since tryAcquireSharedNanos doesn't return seq // retry with minimal wait time. nanos = 1L; } } final boolean isLocked() { return (getState() & 1L) != 0L; } final Thread getOwner() { return (getState() & 1L) == 0L ? null : getExclusiveOwnerThread(); } final long getHoldCount() { return isHeldExclusively() ? holds : 0; } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); holds = 0L; setState(0L); // reset to unlocked state } } private final Sync sync; /** * The default spin value for constructor. Future versions of this * class might choose platform-specific values. Currently, except * on uniprocessors, it is set to a small value that overcomes near * misses between releases and acquires. */ static final int DEFAULT_SPINS = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 0; /** * Creates an instance of {@code SequenceLock} with the default * number of retry attempts to acquire the lock before blocking. */ public SequenceLock() { sync = new Sync(DEFAULT_SPINS); } /** * Creates an instance of {@code SequenceLock} that will retry * attempts to acquire the lock at least the given number of times * before blocking. * * @param spins the number of times before blocking */ public SequenceLock(int spins) { sync = new Sync(spins); } /** * Returns the current sequence number of this lock. The sequence * number is advanced upon each acquire or release action. When * this value is odd, the lock is held; when even, it is released. * * @return the current sequence number */ public long getSequence() { return sync.getSequence(); } /** * Returns the current sequence number when the lock is, or * becomes, available. A lock is available if it is either * released, or is held by the current thread. If the lock is not * available, the current thread becomes disabled for thread * scheduling purposes and lies dormant until the lock has been * released by some other thread. * * @return the current sequence number */ public long awaitAvailability() { return sync.awaitAvailability(); } /** * Returns the current sequence number if the lock is, or * becomes, available within the specified waiting time. * *

If the lock is not available, the current thread becomes * disabled for thread scheduling purposes and lies dormant until * one of three things happens: * *

    * *
  • The lock becomes available, in which case the current * sequence number is returned. * *
  • Some other thread {@linkplain Thread#interrupt interrupts} * the current thread, in which case this method throws * {@link InterruptedException}. * *
  • The specified waiting time elapses, in which case * this method throws {@link TimeoutException}. * *
* * @param timeout the time to wait for availability * @param unit the time unit of the timeout argument * @return the current sequence number if the lock is available * upon return from this method * @throws InterruptedException if the current thread is interrupted * @throws TimeoutException if the lock was not available within * the specified waiting time * @throws NullPointerException if the time unit is null */ public long tryAwaitAvailability(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { return sync.tryAwaitAvailability(unit.toNanos(timeout)); } /** * Acquires the lock. * *

If the current thread already holds this lock then the hold count * is incremented by one and the method returns immediately without * incrementing the sequence number. * *

If this lock not held by another thread, this method * increments the sequence number (which thus becomes an odd * number), sets the lock hold count to one, and returns * immediately. * *

If the lock is held by another thread then the current * thread may retry acquiring this lock, depending on the {@code * spin} count established in constructor. If the lock is still * not acquired, the current thread becomes disabled for thread * scheduling purposes and lies dormant until enabled by * some other thread releasing the lock. */ public void lock() { sync.lock(); } /** * Acquires the lock unless the current thread is * {@linkplain Thread#interrupt interrupted}. * *

If the current thread already holds this lock then the hold count * is incremented by one and the method returns immediately without * incrementing the sequence number. * *

If this lock not held by another thread, this method * increments the sequence number (which thus becomes an odd * number), sets the lock hold count to one, and returns * immediately. * *

If the lock is held by another thread then the current * thread may retry acquiring this lock, depending on the {@code * spin} count established in constructor. If the lock is still * not acquired, the current thread becomes disabled for thread * scheduling purposes and lies dormant until one of two things * happens: * *

    * *
  • The lock is acquired by the current thread; or * *
  • Some other thread {@linkplain Thread#interrupt interrupts} the * current thread. * *
* *

If the lock is acquired by the current thread then the lock hold * count is set to one and the sequence number is incremented. * *

If the current thread: * *

    * *
  • has its interrupted status set on entry to this method; or * *
  • is {@linkplain Thread#interrupt interrupted} while acquiring * the lock, * *
* * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * *

In this implementation, as this method is an explicit * interruption point, preference is given to responding to the * interrupt over normal or reentrant acquisition of the lock. * * @throws InterruptedException if the current thread is interrupted */ public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1L); } /** * Acquires the lock only if it is not held by another thread at the time * of invocation. * *

If the current thread already holds this lock then the hold * count is incremented by one and the method returns {@code true} * without incrementing the sequence number. * *

If this lock not held by another thread, this method * increments the sequence number (which thus becomes an odd * number), sets the lock hold count to one, and returns {@code * true}. * *

If the lock is held by another thread then this method * returns {@code false}. * * @return {@code true} if the lock was free and was acquired by the * current thread, or the lock was already held by the current * thread; and {@code false} otherwise */ public boolean tryLock() { return sync.tryAcquire(1L); } /** * Acquires the lock if it is not held by another thread within the given * waiting time and the current thread has not been * {@linkplain Thread#interrupt interrupted}. * *

If the current thread already holds this lock then the hold count * is incremented by one and the method returns immediately without * incrementing the sequence number. * *

If this lock not held by another thread, this method * increments the sequence number (which thus becomes an odd * number), sets the lock hold count to one, and returns * immediately. * *

If the lock is held by another thread then the current * thread may retry acquiring this lock, depending on the {@code * spin} count established in constructor. If the lock is still * not acquired, the current thread becomes disabled for thread * scheduling purposes and lies dormant until one of three things * happens: * *

    * *
  • The lock is acquired by the current thread; or * *
  • Some other thread {@linkplain Thread#interrupt interrupts} * the current thread; or * *
  • The specified waiting time elapses * *
* *

If the lock is acquired then the value {@code true} is returned and * the lock hold count is set to one. * *

If the current thread: * *

    * *
  • has its interrupted status set on entry to this method; or * *
  • is {@linkplain Thread#interrupt interrupted} while * acquiring the lock, * *
* then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * *

If the specified waiting time elapses then the value {@code false} * is returned. If the time is less than or equal to zero, the method * will not wait at all. * *

In this implementation, as this method is an explicit * interruption point, preference is given to responding to the * interrupt over normal or reentrant acquisition of the lock, and * over reporting the elapse of the waiting time. * * @param timeout the time to wait for the lock * @param unit the time unit of the timeout argument * @return {@code true} if the lock was free and was acquired by the * current thread, or the lock was already held by the current * thread; and {@code false} if the waiting time elapsed before * the lock could be acquired * @throws InterruptedException if the current thread is interrupted * @throws NullPointerException if the time unit is null */ public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1L, unit.toNanos(timeout)); } /** * Attempts to release this lock. * *

If the current thread is the holder of this lock then the * hold count is decremented. If the hold count is now zero then * the sequence number is incremented (thus becoming an even * number) and the lock is released. If the current thread is not * the holder of this lock then {@link * IllegalMonitorStateException} is thrown. * * @throws IllegalMonitorStateException if the current thread does not * hold this lock */ public void unlock() { sync.release(1); } /** * Throws UnsupportedOperationException. SequenceLocks * do not support Condition objects. * * @throws UnsupportedOperationException always */ public Condition newCondition() { throw new UnsupportedOperationException(); } /** * Queries the number of holds on this lock by the current thread. * *

A thread has a hold on a lock for each lock action that is not * matched by an unlock action. * *

The hold count information is typically only used for testing and * debugging purposes. * * @return the number of holds on this lock by the current thread, * or zero if this lock is not held by the current thread */ public long getHoldCount() { return sync.getHoldCount(); } /** * Queries if this lock is held by the current thread. * * @return {@code true} if current thread holds this lock and * {@code false} otherwise */ public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } /** * Queries if this lock is held by any thread. This method is * designed for use in monitoring of the system state, * not for synchronization control. * * @return {@code true} if any thread holds this lock and * {@code false} otherwise */ public boolean isLocked() { return sync.isLocked(); } /** * Returns the thread that currently owns this lock, or * {@code null} if not owned. When this method is called by a * thread that is not the owner, the return value reflects a * best-effort approximation of current lock status. For example, * the owner may be momentarily {@code null} even if there are * threads trying to acquire the lock but have not yet done so. * This method is designed to facilitate construction of * subclasses that provide more extensive lock monitoring * facilities. * * @return the owner, or {@code null} if not owned */ protected Thread getOwner() { return sync.getOwner(); } /** * Queries whether any threads are waiting to acquire this lock. Note that * because cancellations may occur at any time, a {@code true} * return does not guarantee that any other thread will ever * acquire this lock. This method is designed primarily for use in * monitoring of the system state. * * @return {@code true} if there may be other threads waiting to * acquire the lock */ public final boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } /** * Queries whether the given thread is waiting to acquire this * lock. Note that because cancellations may occur at any time, a * {@code true} return does not guarantee that this thread * will ever acquire this lock. This method is designed primarily for use * in monitoring of the system state. * * @param thread the thread * @return {@code true} if the given thread is queued waiting for this lock * @throws NullPointerException if the thread is null */ public final boolean hasQueuedThread(Thread thread) { return sync.isQueued(thread); } /** * Returns an estimate of the number of threads waiting to * acquire this lock. The value is only an estimate because the number of * threads may change dynamically while this method traverses * internal data structures. This method is designed for use in * monitoring of the system state, not for synchronization * control. * * @return the estimated number of threads waiting for this lock */ public final int getQueueLength() { return sync.getQueueLength(); } /** * Returns a collection containing threads that may be waiting to * acquire this lock. Because the actual set of threads may change * dynamically while constructing this result, the returned * collection is only a best-effort estimate. The elements of the * returned collection are in no particular order. This method is * designed to facilitate construction of subclasses that provide * more extensive monitoring facilities. * * @return the collection of threads */ protected Collection getQueuedThreads() { return sync.getQueuedThreads(); } /** * Returns a string identifying this lock, as well as its lock state. * The state, in brackets, includes either the String {@code "Unlocked"} * or the String {@code "Locked by"} followed by the * {@linkplain Thread#getName name} of the owning thread. * * @return a string identifying this lock, as well as its lock state */ public String toString() { Thread o = sync.getOwner(); return super.toString() + ((o == null) ? "[Unlocked]" : "[Locked by thread " + o.getName() + "]"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy