/*
* `gnu.iou'
* Copyright (C) 2006 John Pritchard.
*
* 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., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package gnu.iou ;
import java.util.Hashtable;
/**
* Particular type of locker ("mutex") serializes entry into area(s)
* "protected" by a <code>`lck.serialize()'</code> "gate" or
* "gateway".
*
* <p> The "serialize" function allows one thread past at a time.
* Each thread past "serialize" <b>MUST</b> call "unlock" once it's
* done in the locked area, in order to exit the locked area and to
* allow the next thread in.
*
* <p> If one lock object has its "serialize" gateway used in multiple
* places, then all of those areas will be serialized -- one thread
* enters their collective whole area at a time.
*
* <p> A thread can reenter the serialized area.
*
* <h3>Usage</h3>
*
* <pre>
* new lck();<font color="#af0000">//(reentrant)//</font>
* new lck(true);<font color="#af0000">//(reentrant)//</font>
* new lck(false);<font color="#af0000">//(not reentrant)//</font>
*
* </pre>
*
* <p>For example.
* <p>
* <pre>
* lck locker ;
* try {
* locker.serialize(this);
*
* <font color="#af0000">//(protected region)//</font>
*
* }
* finally {
* locker.unlock(this);
* }
*
* </pre>
*
* @author John Pritchard (john@syntelos.org)
*/
public class lck implements Cloneable {
public static boolean debug = false;
/**
* String lock descriptors set from user code.
*/
private final static Hashtable lck_descriptors = new Hashtable(233);
/**
* Use the `lck' user object to setup a user lock descriptor string.
*
* <p> This does not keep a reference to the user object,
* preventing it from being garbage collected (and finalized).
*
* <p> Call "LckDescRm" from the user- object's "finalize" method.
*
* @see #LckDescRm
*/
public final static void LckDesc ( Object user, String desc){
if ( null != user && null != desc){
Integer ihc = new Integer(System.identityHashCode(user));
lck_descriptors.put( ihc, desc);
}
else
throw new IllegalArgumentException("Null argument to `LckDesc'.");
}
/**
* Use the `lck' user- object to lookup a user lock descriptor string.
*/
public final static String LckDesc ( Object user, lck lock){
if ( null == user){
if ( null == lock)
throw new IllegalArgumentException("Null argument to `LckDesc'.");
else
user = lock;
}
Integer ihc = new Integer(System.identityHashCode(user));
String desc = (String)lck_descriptors.get(ihc);
if ( null == desc){
return chbuf.cat(user.getClass().getName(),"#",ihc.toString());
}
else
return desc;
}
/**
* Use the `lck' user- object to delete a user lock descriptor
* string from cache. (Can call this from the user's "finalize".)
*/
public final static String LckDescRm ( Object user){
if ( null == user)
throw new IllegalArgumentException("Null argument to `LckDesc'.");
else {
Integer ihc = new Integer(System.identityHashCode(user));
return (String)lck_descriptors.remove(ihc);
}
}
private volatile int entered = 0;
private volatile int waiters = 0;
private volatile Thread enteredT = null;
private boolean reenterable = true;
/**
* New locker constructor
*/
public lck(){}
/**
* Define the reentry character of this mutex.
* @param reenterable If true, allow thread reentry as default.
* If false, throw a runtime exception on reentry.
*/
public lck ( boolean reenterable){
super();
this.reenterable = reenterable;
}
/**
* Clone this mutex object into an initialized state.
*/
public lck copy(){
try {
return (lck)this.clone();
}
catch (CloneNotSupportedException cx){
throw new IllegalStateException();
}
}
/**
* Clone this mutex object into an initialized state.
*/
protected Object clone() throws CloneNotSupportedException {
lck clo = (lck)super.clone();
clo.entered = 0;
clo.waiters = 0;
clo.enteredT = null;
return clo;
}
/**
* The "lck" gate function. Each method calls this typically on
* entering a "lck"- protected area. Only one thread is allowed
* past at a time.
*
* <pre>
* lck locker;
* try {
* locker.serialize(this);
*
* <font color="#af0000">//(protected region)//</font>
*
* }
* finally {
* locker.unlock(this);
* }
* </pre>
*
* @param user The object maintaining and using the lock. The
* object may have a <tt>"LckDesc"</tt> record for debugging the
* lock.
*
* @see #LckDesc
*/
public final synchronized void serialize( Object user){
Thread ct = Thread.currentThread();
if ( this.enteredT == ct){
if (debug) System.out.println( chbuf.cat("LCK ",ct.getName()," SERIALIZE REENTER (",LckDesc(user,this),") ",bpo.atStack(new Exception(),2)));
if (this.reenterable)
return ; // re- entry
else
throw new RuntimeException("Reentering mutex.");
}
else {
this.waiters += 1;
while (0 < this.entered){
if (debug) System.out.println(chbuf.cat("LCK ",ct.getName()," SERIALIZE WAIT (",LckDesc(user,this),") WAITING ",bpo.atStack(new Exception(),2)));
try {
this.wait();
} catch ( InterruptedException intx){
if (debug) System.out.println(chbuf.cat("LCK ",ct.getName()," SERIALIZE WAIT-INTERRUPTED (",LckDesc(user,this),") WAITING ",bpo.atStack(new Exception(),2)));
}
}
if (debug) System.out.println(chbuf.cat("LCK ",ct.getName()," SERIALIZE EXIT (",LckDesc(user,this),") ",bpo.atStack(new Exception(),2)));
// if (null != enteredT)
// throw new RuntimeException(chbuf.cat("LCK (",LckDesc(user,this),"@",ct.getName(),") is broken?!"));
synchronized(this){
this.waiters -= 1;
this.entered += 1;
this.enteredT = ct;
}
}
}
/**
* The "lck" gate keeper function. Each thread exiting the "lck"-
* protected area calls on this unlock function to allow the next
* thread to enter the protected ("serialized") area.
*
* <pre>
* lck locker;
* try {
* locker.serialize(this);
*
* <font color="#af0000">//(protected region)//</font>
*
* }
* finally {
* locker.unlock(this);
* }
* </pre>
*
* @param user The object maintaining and using the lock. The
* object may have a <tt>"LckDesc"</tt> record for debugging the
* lock.
*
* @see #LckDesc
*/
public final synchronized void unlock( Object user){
if (null != this.enteredT){
Thread ct = Thread.currentThread();
if (debug) System.out.println(chbuf.cat("LCK ",ct.getName()," UNLOCK (",LckDesc(user,this),") ",bpo.atStack(new Exception(),2)));
this.entered -= 1;
if (this.reenterable){
if ( 0 == this.entered){
if (ct == this.enteredT)
this.enteredT = null;
else
throw new IllegalStateException("Thread unlocking reentrant ["+ct.getName()+" != "+this.enteredT.getName()+"] "+bpo.atStack(new Exception(),2));
}
}
else
this.enteredT = null;
if ( 0 < this.waiters)
this.notify();
}
}
/**
* If a thread has entered `serialized', the lock is considered "locked".
*/
public final synchronized boolean isLocked(){ return (0 != this.entered);}
/**
* Number of threads entered past this lock. Should be zero or
* one! */
public final synchronized int entered(){ return this.entered;}
/**
* Number of threads waiting on this lock (waiting behind the
* gate). */
public final synchronized int waiters(){ return this.waiters;}
/**
* Classname with "entered" parameter in usual "java" square
* bracket format. */
public String toString(){
return chbuf.cat(getClass().getName(),"[entered=",Integer.toString(entered),",waiters=",Integer.toString(waiters),"]");
}
private static class test implements Runnable {
private lck locker = new lck();
private int count;
test(int N){
super();
this.count = N;
}
private long pseudo(){
int re = this.count;
if (10 > re){
this.count = 9;
return 19;
}
else {
this.count = (re-1);
return (re+10);
}
}
private void enter(){
this.locker.serialize(Thread.currentThread());
}
private void exit(){
this.locker.unlock(Thread.currentThread());
}
public void run(){
try {
Thread.sleep(this.pseudo());
}
catch (InterruptedException ix){}
this.enter();
System.out.println(Thread.currentThread().getName());
this.exit();
}
}
private static void usage(){
System.out.println();
System.out.println("usage: gnu.iou.lck N");
System.out.println();
System.exit(1);
}
public static void main(String[] argv){
if (null == argv || 1 != argv.length)
usage();
else {
try {
int N = Integer.parseInt(argv[0]);
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
test to = new test(N);
Thread T;
for (int cc = 0; cc < N; cc++){
T = new Thread(to);
T.setPriority(Thread.MIN_PRIORITY);
T.start();
}
}
catch (NumberFormatException nfx){
usage();
}
}
}
}