/* * org.openmicroscopy.shoola.util.concur.Semaphore * *------------------------------------------------------------------------------ * Copyright (C) 2006 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.util.concur; //Java imports //Third-party libraries //Application-internal dependencies /** * Emulates the functionality of a basic counting semaphore. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author <br>Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk"> * a.falconi@dundee.ac.uk</a> * @version 2.2 * <small> * (<b>Internal version:</b> $Revision$ $Date$) * </small> * @since OME2.2 */ public class Semaphore { /** * Counts the permissions to proceed that are currently available. * A non-positive count signifies that no permission is available. * The count can be initialized to a negative value <code>n</code>, * in which case a down blocks until <code>-n+1</code> up are made. * After the count has reached a non-negative value, it will never * fall under zero again. */ private int count; /** * Attempts to decrease the permissions {@link #count} by one and * optionally waits <code>timeout</code> milliseconds if not successful. * This method assumes the caller already owns the object's monitor — * that is, call this method only within a <code>synchronized</code> * block. * * @param timeout The amount of milliseconds the calling thread should be * suspended if the {@link #count} couldn't be decreased. * Pass <code>0</code> for an unbounded wait or <code>-1</code> * to not wait at all. * @return <code>true</code> if the {@link #count} was decreased, * <code>false</code> otherwise. * @throws InterruptedException If the current thread has been interrupted. */ private boolean decreaseCount(long timeout) throws InterruptedException { //We own the object's monitor. if (0 < count) { --count; return true; } if (0 <= timeout) { //Don't wait if < 0. try { wait(timeout); //Wait for unbounded time if 0. } catch (InterruptedException ie) { //Rely signal in the case interruption and notification happened //in the wrong order. (JVM makes no guarantees on which action //takes precedence if both notification and interruption happen //at about the same time when the thread is suspended.) notify(); throw ie; } } return false; } /** * Creates a new instance with the specified initial number of permisions * to proceed. * You can specify this initial number to be <code>1</code> in order to * emulate a mutex. A negative value <code>n</code> is also possible, * in which case a {@link #down() down} blocks until <code>-n+1</code> * {@link #up() up}'s are made. After the count has reached a non-negative * value, it will never fall under zero again. * * @param initialCount Initial number of permissions. */ public Semaphore(int initialCount) { count = initialCount; } /** * Atomically acquires a permission to proceed. * If no permission is currently available, the caller is suspended until * one is made available by some other thread calling the {@link #up() up} * method. * * @throws InterruptedException If the current thread has been interrupted. * @see #down(long) */ public void down() throws InterruptedException { //Don't acquire the lock if the current thread has been interrupted. //Pointless to have this thread compete to acquire the lock and then //possibly suspend when it should stop its activity instead. if (Thread.interrupted()) throw new InterruptedException(); //Get the lock, but release it and suspend if not allowed to proceed. synchronized (this) { if (flowObs != null) flowObs.update(LOCK_ACQUIRED); while (!decreaseCount(0)) ; //Wait until we can decrease count. } } /** * Atomically acquires a permission to proceed. * If no permission is currently available, the caller is suspended until * one is made available by some other thread calling the {@link #up() up} * method or until the specified <code>timeout</code> elapses. * * @param timeout The amount of milliseconds the calling thread should be * suspended if no permission can be acquired. If the passed * value is not positive, then the caller is not suspended * at all. * @return <code>true</code> if a permission was acquired, * <code>false</code> otherwise. * @throws InterruptedException If the current thread has been interrupted. * @see #down() */ public boolean down(long timeout) throws InterruptedException { //Don't acquire the lock if the current thread has been interrupted. //Pointless to have this thread compete to acquire the lock and then //possibly suspend when it should stop its activity instead. if (Thread.interrupted()) throw new InterruptedException(); //Get the lock. synchronized (this) { if (flowObs != null) flowObs.update(LOCK_ACQUIRED); if (decreaseCount(-1)) //Attempt to decrease without waiting. return true; if (timeout <= 0) //No wait. We couldn't decrease so return false. return false; //Release lock and suspend until allowed to proceed or timed-out. long start = System.currentTimeMillis(), delta = timeout; while (true) { if (decreaseCount(delta)) return true; delta = (start+timeout) - System.currentTimeMillis(); if (delta <= 0) return false; } } } /** * Atomically releases a permission to proceed. * If any thread is currently suspended in a {@link #down() down}, then * on is awaken and allowed to proceed. */ public void up() { synchronized (this) { if (flowObs != null) flowObs.update(LOCK_ACQUIRED); ++count; //As we added just one to the count, there can be at most one //waiting thread that may proceed, so it's pointless (and far //more expensive) to notify all threads in the wait set. notify(); } } /* * ============================================================== * Follows code to enable testing. * ============================================================== */ static final int LOCK_ACQUIRED = 1; private ControlFlowObserver flowObs; synchronized int getCount() { return count; } void register(ControlFlowObserver obs) { flowObs = obs; } }