/* * Sun Public License * * 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 the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.common; /** * This class implements a mutex based on reference counts so that multiple * readers may hold the lock at the same time, but a writer must have exclusive * access to the lock (that is, no read locks may be in use while a write lock * is held). * * * @author Neil A. Wilson */ public class RefCountMutex { // Indicates whether this private final boolean debugMode; // Indicates whether the write lock is currently being held. private boolean writeLockHeld; // Used to keep track of the number of read locks currently held. private int referenceCount; // The mutex used to block lock requests while a write lock has been granted. private final Object lockRequestedMutex; // The mutex used to provide threadsafe access to the reference count. private final Object referenceCountMutex; // The mutex used to ensure that only a single write lock can be held at any // given time. private final Object writeLockMutex; /** * Creates a new reference count mutex. */ public RefCountMutex() { debugMode = false; writeLockHeld = false; referenceCount = 0; lockRequestedMutex = new Object(); referenceCountMutex = new Object(); writeLockMutex = new Object(); } /** * Creates a new reference count mutex, optionally operating in debug mode. * * @param debugMode Indicates debug logging should be performed. */ public RefCountMutex(boolean debugMode) { this.debugMode = debugMode; writeLockHeld = false; referenceCount = 0; lockRequestedMutex = new Object(); referenceCountMutex = new Object(); writeLockMutex = new Object(); } /** * Obtains a read lock. This method will not return until the read lock has * been granted. */ public void getReadLock() { debugPrint("In getReadLock()"); synchronized (lockRequestedMutex) { boolean lockHeld = true; while (lockHeld) { synchronized (writeLockMutex) { lockHeld = writeLockHeld; } if (lockHeld) { try { Thread.sleep(Constants.THREAD_BLOCK_SLEEP_TIME); } catch (InterruptedException ie) {} } } synchronized (referenceCountMutex) { referenceCount++; debugPrint("Successfully got a read lock -- refcount is " + referenceCount); } } } /** * Releases a previously obtained read lock. */ public void releaseReadLock() { debugPrint("In releaseReadLock()"); synchronized (referenceCountMutex) { referenceCount--; debugPrint("Released a read lock -- refcount is " + referenceCount); // If this happens, then it means that there was a case in which a // read lock was released multiple times. if (referenceCount < 0) { referenceCount = 0; debugPrint("Reset the refcount to zero"); } } } /** * Obtains a write lock. This method will not return until the write lock has * been granted. */ public void getWriteLock() { debugPrint("In getWriteLock()"); synchronized (lockRequestedMutex) { boolean readHeld = true; boolean writeHeld = true; while (readHeld || writeHeld) { synchronized (referenceCountMutex) { debugPrint("Blocking until the lock can be acquired -- refcount is " + referenceCount); readHeld = (referenceCount > 0); } if (! readHeld) { synchronized (writeLockMutex) { writeHeld = writeLockHeld; if (! writeHeld) { writeLockHeld = true; } } } if (readHeld || writeHeld) { try { Thread.sleep(Constants.THREAD_BLOCK_SLEEP_TIME); } catch (InterruptedException ie) {} } } } debugPrint("Successfully obtained the write lock."); } /** * Obtains a write lock. This method will not return until the write lock has * been granted, or until the specified timeout occurs. * * @param timeout The maximum length of time (in milliseconds) to wait on * the write lock. * * @throws InterruptedException If the write lock could not be obtained * before the timeout occurred. */ public void getWriteLock(long timeout) throws InterruptedException { debugPrint("In getWriteLock()"); boolean interrupted = true; long interruptTime = System.currentTimeMillis() + timeout; synchronized (lockRequestedMutex) { boolean readHeld = true; boolean writeHeld = true; while ((readHeld || writeHeld) && (System.currentTimeMillis() < interruptTime)) { synchronized (referenceCountMutex) { debugPrint("Blocking until the lock can be acquired -- refcount is " + referenceCount); readHeld = (referenceCount > 0); } if (! readHeld) { synchronized (writeLockMutex) { writeHeld = writeLockHeld; if (! writeHeld) { interrupted = false; writeLockHeld = true; } } } if (readHeld || writeHeld) { try { Thread.sleep(Constants.THREAD_BLOCK_SLEEP_TIME); } catch (InterruptedException ie) {} } } } if (interrupted) { throw new InterruptedException("Unable to obtain the write lock " + "before the specified timeout."); } debugPrint("Successfully obtained the write lock."); } /** * Releases a previously obtained write lock. */ public void releaseWriteLock() { debugPrint("In releaseWriteLock()"); synchronized (writeLockMutex) { writeLockHeld = false; } debugPrint("Successfully released the write lock."); } /** * Prints the specified message to standard error if debug mode is enabled. * If debug mode is not enabled, then nothing will be printed. * * @param message The message to be printed. */ public void debugPrint(String message) { if (debugMode) { System.err.println(message); } } }