/* * 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. */ // XXX document M.P fully // XXX uses: FolderChildren, Children.MutexChildren, module system // XXX deadlock analyzer on system exit package org.openide.util; import java.awt.EventQueue; import java.util.*; import java.lang.reflect.InvocationTargetException; /** 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 probably not starve a writer or reader indefinitely, * but the exact behavior is at the mercy of {@link Object#wait()} and {@link Object#notifyAll()}. *
* 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("EVENT", Integer.MAX_VALUE); // NOI18N
private static final RequestProcessor LATER = new RequestProcessor("Mutex"); // NOI18N
private final int level;
private final String name;
/** 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
* @deprecated Does not permit specification of levels, and cannot work with
* {@link #canRead} and {@link #canWrite} correctly.
*/
public Mutex (Object lock) {
this.lock = lock;
level = Integer.MAX_VALUE;
name = lock.toString();
}
/** Default constructor.
* @deprecated Does not permit specification of levels.
*/
public Mutex() {
this(new InternalLock());
}
/** @param privileged can enter privileged states of this Mutex
* This helps avoid creating of custom Runnables.
* @deprecated Does not permit specification of levels.
*/
public Mutex(Privileged privileged) {
this();
init(privileged);
}
/**
* Create a mutex with a defined level.
*/
public Mutex(String name, int level) {
if (level == Integer.MAX_VALUE && EVENT != null) throw new IllegalArgumentException();
if (name == null) throw new IllegalArgumentException();
this.name = name;
this.lock = new InternalLock();
this.level = level;
}
/**
* Create a mutex with a privileged key and a defined level.
*/
public Mutex(String name, Privileged privileged, int level) {
this(name, level);
init(privileged);
}
private void init(Privileged privileged) {
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) {
checkEnter(this);
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 || canRead();
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);
}
if (lock instanceof InternalLock) {
enterWriteAccess();
try {
return action.run();
} finally {
exitWriteAccess();
}
} else {
// Same but synch on lock.
synchronized (lock) {
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);
}
if (lock instanceof InternalLock) {
enterWriteAccess();
try {
return action.run();
} catch (Exception e) {
throw new MutexException(e);
} finally {
exitWriteAccess();
}
} else {
// Same but synch on lock.
synchronized (lock) {
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) {
if (lock instanceof InternalLock) {
enterWriteAccess();
} else {
try {
action.run();
} finally {
exitWriteAccess();
}
}
}
}
if (synch && lock instanceof InternalLock) {
try {
action.run();
} finally {
exitWriteAccess();
}
} else {
LATER.post(new Runnable() {
public void run() {
if (lock instanceof InternalLock) {
enterWriteAccess();
try {
action.run();
} finally {
exitWriteAccess();
}
} else {
synchronized (lock) {
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.live) 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. * * You may not call this method if you already have the read mutex * (except nested inside a write mutex). * *Warning: this method blocks.
* @param run runnable to run */ public void postWriteRequest (Runnable run) { if (this == EVENT) { eventAccess(run, false); return; } if (canWrite()) { run.run(); } else if (canRead()) { // Cannot do it now; do it when exiting outermost read lock. synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (!ti.live) throw new IllegalStateException(); if (ti.lateWriteActions == null) { ti.lateWriteActions = new LinkedList(); } ti.lateWriteActions.add(run); } } else { if (lock instanceof InternalLock) { enterWriteAccess(); try { run.run(); } finally { exitWriteAccess(); } } else { synchronized (lock) { enterWriteAccess(); try { run.run(); } finally { exitWriteAccess(); } } } } } /** * Enter the read mutex. * @see Mutex.Privileged#enterReadAccess */ void enterReadAccess() { synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti.live) { // Already in read or write mutex, can certainly enter read. ti.extraReads++; } else { // ti == null, entering fresh. // Wait for any writers to exit. checkEnter(this); while (semaphore < 0) { try { lock.wait(); } catch (InterruptedException e) { throw new IllegalStateException(e.toString()); } } // Uncontended or shared, go ahead. ti.register(false); semaphore++; } } } /** * Exit the read mutex. * @see Mutex.Privileged#exitReadAccess */ void exitReadAccess() { // This method is a little more complicated than is comfortable. // 1. A top-level read mutex is being exited. Fine. // 1a. There were some late write actions; grab the write lock, run them, release it. // 2. A nested read mutex is being exited. OK. // 3. A top-level read mutex inside a write mutex is being exited. OK. // 3a. There were some late write actions; run them now. // 4. A nested read mutex inside a write mutex is being exited. OK. List lateWriteActions = null; boolean wasWriter = false; synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (!ti.live) throw new IllegalStateException(); if (ti.extraReads > 0) { // Cases 2, 3, or 4. if (semaphore == 0) throw new IllegalStateException(); // Just mark them off. ti.extraReads--; if (ti.extraReads == 0 && ti.writer && ti.lateWriteActions != null) { // Case 3a. We are a writer who has just finished nested reads and now can // run some late write actions. lateWriteActions = ti.lateWriteActions; ti.lateWriteActions = null; wasWriter = true; // We exited the lock, but since we are a writer no one else can enter // anything, so that is safe. } } else { // Case 1. if (ti.writer) throw new IllegalStateException(); if (semaphore <= 0) throw new IllegalStateException(); if (ti.extraWrites > 0) throw new IllegalStateException(); // Really exiting. Case 1. if (ti.lateWriteActions != null) { // We are a reader who has just finished all nested reads and now can // run some late write actions. Case 1a. lateWriteActions = ti.lateWriteActions; ti.lateWriteActions = null; wasWriter = false; } ti.unregister(); semaphore--; lock.notifyAll(); if (semaphore == 0 && threadCount > 0) throw new IllegalStateException(toString()); if (lateWriteActions != null && !(lock instanceof InternalLock)) { // Have to run them with lock still held. enterWriteAccess(); try { Iterator it = lateWriteActions.iterator(); while (it.hasNext()) { Runnable r = (Runnable)it.next(); r.run(); } } finally { exitWriteAccess(); lateWriteActions = null; } } } } if (lateWriteActions != null) { if (!wasWriter) { enterWriteAccess(); } try { Iterator it = lateWriteActions.iterator(); while (it.hasNext()) { Runnable r = (Runnable)it.next(); r.run(); } } finally { if (!wasWriter) { exitWriteAccess(); } } } } /** * Enter the write mutex. * @see Mutex.Privileged#enterWriteAccess */ void enterWriteAccess() { synchronized (lock) { ThreadInfo ti = currentThreadInfo(); if (ti.live) { if (ti.writer && ti.extraReads == 0) { // Already in write mutex, can reenter freely. ti.extraWrites++; } else { throw new IllegalStateException("Illegal mutex upgrade from read to write in " + name); // NOI18N } } else { // ti == null, entering fresh. // Wait for any readers or writers to exit. checkEnter(this); while (semaphore != 0) { try { lock.wait(); } catch (InterruptedException e) { throw new IllegalStateException(e.toString()); } } // Uncontended, go ahead. ti.register(true); semaphore = -1; } } } /** * Exit the write mutex. * @see Mutex.Privileged#exitWriteAccess */ void exitWriteAccess() { List lateReadActions = null; ThreadInfo ti; synchronized (lock) { ti = currentThreadInfo(); if (!ti.live) throw new IllegalStateException(); if (!ti.writer) throw new IllegalStateException(); if (semaphore != -1) throw new IllegalStateException(); if (ti.extraReads > 0) throw new IllegalStateException(); if (ti.extraWrites > 0) { // Just mark them off. ti.extraWrites--; } else if (ti.lateReadActions != null) { if (threadCount != 1) throw new IllegalStateException(toString()); // Will exit after running these. lateReadActions = ti.lateReadActions; ti.lateReadActions = null; semaphore = 1; ti.writer = false; lock.notifyAll(); // Now the semaphore is released, we are in plain read access. // No other writers can enter before this because we have not yet // released the lock. Other readers can however enter. } else { // Really exiting. ti.unregister(); semaphore = 0; lock.notifyAll(); if (threadCount > 0) throw new IllegalStateException(toString()); } } if (lateReadActions != null) { 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. * You may not call this on a mutex using a {@link #Mutex(Object} special lock}. * @since XXX */ public boolean canRead() { if (this == EVENT) { return isDispatchThread(); } if (!(lock instanceof InternalLock)) { throw new IllegalStateException("Mutex " + name + " uses a special lock, cannot call canRead"); // NOI18N } synchronized (lock) { if (semaphore == 0) { // Uncontended, obviously not. Only a shortcut, // next clause would catch it anyway. return false; } return currentThreadInfo().live; } } /** * Check if the current thread is holding the write mutex. * You may not call this on a mutex using a {@link #Mutex(Object} special lock}. * @since XXX */ public boolean canWrite() { if (this == EVENT) { return isDispatchThread(); } if (!(lock instanceof InternalLock)) { throw new IllegalStateException("Mutex " + name + " uses a special lock, cannot call canWrite"); // NOI18N } 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.live) { if (!ti.writer) throw new IllegalStateException(); return ti.extraReads == 0; } else { return false; } } } /** toString */ public String toString() { if (this == EVENT) { return "Mutex.EVENT"; // NOI18N } synchronized (lock) { StringBuffer b = new StringBuffer("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) { if (this.parent != null) throw new IllegalStateException(); this.parent = parent; } public void enterReadAccess() { parent.enterReadAccess(); } public void enterWriteAccess() { parent.enterWriteAccess(); } public void exitReadAccess() { parent.exitReadAccess(); } public void exitWriteAccess() { parent.exitWriteAccess(); } } }