/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.util; import java.awt.EventQueue; import java.util.*; import java.lang.reflect.InvocationTargetException; import org.openide.ErrorManager; /** Read-many/write-one lock. * Allows control over resources that * can be read by several readers at once but only written by one writer. *
* It is guaranteed that if you are a writer you can also enter the * mutex as a reader. But you cannot enter the write mutex if you hold * the read mutex, since that can cause deadlocks. *
* This implementation will not starve a writer or reader indefinitely. *
* Examples of use: * *
*
* @author Ales Novak, Jesse Glick
*/
public final class Mutex extends Object {
/** Mutex that allows code to be synchronized with the AWT event dispatch thread. */
public static final Mutex EVENT = new Mutex ();
private static final RequestProcessor LATER = new RequestProcessor("Mutex"); // NOI18N
/** Enhanced constructor that permits specifying an object to use as a lock.
* The lock is used on entry and exit to {@link #readAccess} and during the
* whole execution of {@link #writeAccess}. The ability to specify locks
* allows several
* Mutex m = new Mutex ();
*
* // Grant write access, compute an integer and return it:
* return (Integer)m.writeAccess (new Mutex.Action () {
* public Object run () {
* return new Integer (1);
* }
* });
*
* // Obtain read access, do some computation, possibly throw an IOException:
* try {
* m.readAccess (new Mutex.ExceptionAction () {
* public Object run () throws IOException {
* if (...) throw new IOException ();
*
* return null;
* }
* });
* } catch (MutexException ex) {
* throw (IOException)ex.getException ();
* }
*
Mutex
es to synchronize on one object or to synchronize
* a mutex with another critical section.
*
* @param lock lock to use
*/
public Mutex (Object lock) {
this.lock = lock;
}
/** Default constructor.
*/
public Mutex() {
this(new InternalLock());
}
/** @param privileged can enter privileged states of this Mutex
* This helps avoid creating of custom Runnables.
*/
public Mutex(Privileged privileged) {
this();
if (privileged == null) {
throw new IllegalArgumentException("privileged == null"); //NOI18N
} else {
privileged.setParent(this);
}
}
/** Run an action only with read access.
* See class description re. entering for write access within the dynamic scope.
* @param action the action to perform
* @return the object returned from {@link Mutex.Action#run}
*/
public Object readAccess (Action action) {
if (this == EVENT) {
return eventAccess(action);
}
enterReadAccess();
try {
return action.run();
} finally {
exitReadAccess();
}
}
/** Run an action with read access and possibly throw a checked exception.
* The exception if thrown is then encapsulated
* in a MutexException
and thrown from this method. One is encouraged
* to catch MutexException
, obtain the inner exception, and rethrow it.
* Here is an example:
*
* Note that runtime exceptions are always passed through, and neither
* require this invocation style, nor are encapsulated.
* @param action the action to execute
* @return the object returned from {@link Mutex.ExceptionAction#run}
* @exception MutexException encapsulates a user exception
* @exception RuntimeException if any runtime exception is thrown from the run method
* @see #readAccess(Mutex.Action)
*/
public Object readAccess (ExceptionAction action) throws MutexException {
if (this == EVENT) {
return eventAccess(action);
}
enterReadAccess();
try {
return action.run();
} catch (Exception e) {
throw new MutexException(e);
} finally {
exitReadAccess();
}
}
/** Run an action with read access, returning no result.
* It may be run asynchronously (or not) at the discretion of the implementation.
* This implementation tries to run if synchronously if it will not have to wait
* for a lock, else runs it at some later point in the future.
* @param action the action to perform
* @see #readAccess(Mutex.Action)
*/
public void readAccess (final Runnable action) {
if (this == EVENT) {
eventAccess(action, true);
return;
}
boolean synch;
synchronized (lock) {
synch = semaphore >= 0;
if (synch) {
enterReadAccess();
}
}
if (synch) {
try {
action.run();
} finally {
exitReadAccess();
}
} else {
LATER.post(new Runnable() {
public void run() {
enterReadAccess();
try {
action.run();
} finally {
exitReadAccess();
}
}
});
}
}
/** Run an action with write access.
* The same thread may meanwhile reenter the mutex; see the class description for details.
*
* @param action the action to perform
* @return the result of {@link Mutex.Action#run}
*/
public Object writeAccess (Action action) {
if (this == EVENT) {
return eventAccess(action);
}
enterWriteAccess();
try {
return action.run();
} finally {
exitWriteAccess();
}
}
/** Run an action with write access and possibly throw an exception.
* Here is an example:
*
* try {
* mutex.readAccess (new ExceptionAction () {
* public void run () throws IOException {
* throw new IOException ();
* }
* });
* } catch (MutexException ex) {
* throw (IOException) ex.getException ();
* }
*
*
* @param action the action to execute
* @return the result of {@link Mutex.ExceptionAction#run}
* @exception MutexException an encapsulated checked exception, if any
* @exception RuntimeException if a runtime exception is thrown in the action
* @see #writeAccess(Mutex.Action)
* @see #readAccess(Mutex.ExceptionAction)
*/
public Object writeAccess (ExceptionAction action) throws MutexException {
if (this == EVENT) {
return eventAccess(action);
}
enterWriteAccess();
try {
return action.run();
} catch (Exception e) {
throw new MutexException(e);
} finally {
exitWriteAccess();
}
}
/** Run an action with write access and return no result.
* It may be run asynchronously (or not) at the discretion of the implementation.
* This implementation tries to run if synchronously if it will not have to wait
* for a lock, else runs it at some later point in the future.
* @param action the action to perform
* @see #writeAccess(Mutex.Action)
* @see #readAccess(Runnable)
*/
public void writeAccess (final Runnable action) {
if (this == EVENT) {
eventAccess(action, true);
return;
}
boolean synch;
synchronized (lock) {
synch = semaphore == 0 || canWrite();
if (synch) {
enterWriteAccess();
}
}
if (synch) {
try {
action.run();
} finally {
exitWriteAccess();
}
} else {
LATER.post(new Runnable() {
public void run() {
enterWriteAccess();
try {
action.run();
} finally {
exitWriteAccess();
}
}
});
}
}
/** Posts a read request. This request runs immediately iff
* this Mutex is in the shared mode or this Mutex is not contended
* at all.
*
* This request is delayed if this Mutex is in the exclusive
* mode and is held by this thread, until the exclusive is left.
*
* Finally, this request blocks, if this Mutex is in the exclusive
* mode and is held by another thread.
*
*
* try {
* mutex.writeAccess (new ExceptionAction () {
* public void run () throws IOException {
* throw new IOException ();
* }
* });
* } catch (MutexException ex) {
* throw (IOException) ex.getException ();
* }
*
Warning: this method blocks.
* * @param run runnable to run */ public void postReadRequest (final Runnable run) { if (this == EVENT) { eventAccess(run, false); return; } if (canWrite()) { synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti == null) throw new IllegalStateException(); if (!ti.writer) throw new IllegalStateException(); if (ti.lateReadActions == null) { ti.lateReadActions = new LinkedList(); } ti.lateReadActions.add(run); } } else { enterReadAccess(); try { run.run(); } finally { exitReadAccess(); } } } /** Posts a write request. This request runs immediately iff * this Mutex is in the "pure" exclusive mode, i.e. this Mutex * is not reentered in shared mode after the exclusive mode * was acquired. Otherwise it is delayed until all read requests * are executed. * * This request runs immediately if this Mutex is not contended at all. * * This request blocks if this Mutex is in the shared mode. * *Warning: this method blocks.
* @param run runnable to run */ public void postWriteRequest (Runnable run) { if (this == EVENT) { eventAccess(run, false); return; } if (canWrite()) { boolean synch; synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti == null) throw new IllegalStateException(); if (!ti.writer) throw new IllegalStateException(); synch = ti.extraReads == 0; if (!synch) { if (ti.lateWriteActions == null) { ti.lateWriteActions = new LinkedList(); } ti.lateWriteActions.add(run); } } if (synch) { run.run(); } } else if (canRead()) { throw new IllegalStateException("Cannot call postWriteRequest while holding a read mutex; consider using writeAccess(Runnable)"); // NOI18N } else { enterWriteAccess(); try { run.run(); } finally { exitWriteAccess(); } } } /** * Enter the read mutex. * @see Mutex.Privileged#enterReadAccess */ void enterReadAccess() { synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti != null) { // Already in read or write mutex, can certainly enter read. ti.extraReads++; } else { // ti == null, entering fresh. // Wait for any writers to exit. while (semaphore < 0) { try { lock.wait(); } catch (InterruptedException e) { throw new IllegalStateException(e.toString()); } } // Uncontended or shared, go ahead. new ThreadInfo(false).register(); semaphore++; } } } /** * Exit the read mutex. * @see Mutex.Privileged#exitReadAccess */ void exitReadAccess() { List lateWriteActions = null; synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti == null) throw new IllegalStateException(); if (ti.extraReads > 0) { // Just mark them off. ti.extraReads--; if (ti.extraReads == 0 && ti.writer && ti.lateWriteActions != null) { // We are a writer who has just finished nested reads and now can // run some late write actions. if (semaphore != -1) throw new IllegalStateException(); lateWriteActions = ti.lateWriteActions; ti.lateWriteActions = null; } } else { // Really exiting. if (ti.writer) throw new IllegalStateException(); if (semaphore <= 0) throw new IllegalStateException(); if (ti.extraWrites > 0) throw new IllegalStateException(); ti.unregister(); semaphore--; lock.notifyAll(); if (semaphore == 0 && !threads.isEmpty()) throw new IllegalStateException(threads.toString()); } } if (lateWriteActions != null) { // We exited the lock, but since are a writer no one else can enter // anything, so that is safe. Iterator it = lateWriteActions.iterator(); while (it.hasNext()) { Runnable r = (Runnable)it.next(); r.run(); } } } /** * Enter the write mutex. * @see Mutex.Privileged#enterWriteAccess */ void enterWriteAccess() { synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti != null) { if (ti.writer) { // Already in write mutex, can reenter freely. ti.extraWrites++; } else { throw new IllegalStateException("Illegal mutex upgrade from read to write"); // NOI18N } } else { // ti == null, entering fresh. // Wait for any readers or writers to exit. while (semaphore != 0) { try { lock.wait(); } catch (InterruptedException e) { throw new IllegalStateException(e.toString()); } } // Uncontended, go ahead. new ThreadInfo(true).register(); semaphore = -1; } } } /** * Exit the write mutex. * @see Mutex.Privileged#exitWriteAccess */ void exitWriteAccess() { List lateReadActions = null; ThreadInfo ti; synchronized (lock) { ti = currentThreadInfo(); if (ti == null) throw new IllegalStateException(); if (!ti.writer) throw new IllegalStateException(); if (semaphore != -1) throw new IllegalStateException(); if (ti.extraWrites > 0) { // Just mark them off. ti.extraWrites--; } else if (ti.lateReadActions != null) { if (ti.extraReads > 0) throw new IllegalStateException(); // Will exit after running these. lateReadActions = ti.lateReadActions; ti.lateReadActions = null; } else { // Really exiting. if (ti.extraReads > 0) throw new IllegalStateException(); ti.unregister(); semaphore = 0; lock.notifyAll(); if (!threads.isEmpty()) throw new IllegalStateException(threads.toString()); } } if (lateReadActions != null) { // No other threads can enter before this because we have not yet // released the semaphore. synchronized (lock) { semaphore = 1; ti.writer = false; lock.notifyAll(); // Now the semaphore is released, we are in plain read access. if (threads.size() != 1) throw new IllegalStateException(threads.toString()); } try { Iterator it = lateReadActions.iterator(); while (it.hasNext()) { Runnable r = (Runnable)it.next(); r.run(); } } finally { exitReadAccess(); } } } /** * Check if the current thread is holding a read or write mutex. * @since XXX */ public boolean canRead() { if (this == EVENT) { return isDispatchThread(); } synchronized (lock) { if (semaphore == 0) { // Uncontended, obviously not. Only a shortcut, // next clause would catch it anyway. return false; } return currentThreadInfo() != null; } } /** * Check if the current thread is holding the write mutex. * @since XXX */ public boolean canWrite() { if (this == EVENT) { return isDispatchThread(); } synchronized (lock) { if (semaphore != -1) { // Not in write mode, obviously not. Only a shortcut, // next clause would catch it anyway. return false; } ThreadInfo ti = currentThreadInfo(); if (ti != null) { if (!ti.writer) throw new IllegalStateException(); return true; } else { return false; } } } /** toString */ public String toString() { if (this == EVENT) { return "Mutex.EVENT"; // NOI18N } return "Mutex* try { * enterXAccess (); * yourCustomMethod (); * } finally { * exitXAccess (); * } ** can be used. * * You must, however, control the related Mutex, i.e. you must be creator of * the Mutex. * * @since 1.17 */ public static final class Privileged { private Mutex parent; final void setParent(Mutex parent) { this.parent = parent; } public void enterReadAccess() { parent.enterReadAccess(); } public void enterWriteAccess() { parent.enterWriteAccess(); } public void exitReadAccess() { parent.exitReadAccess(); } public void exitWriteAccess() { parent.exitWriteAccess(); } } }