|
To Slava
NOTE
JAVA IMPLEMENTATION OF ACTIVE OBJECT: DINING PHILOSOPHER PROBLEM
INTRODUCTION
Concept of Active Object represented in Dr. M.Samek book [1] is very convenient design template for real-time system. Active Object provides flexible and safe mechanism for synchronization of concurrently running processes. This is a main advantage of the approach that is achieved by strict accomplishment of OOP principles. Namely, Active Object does not share any data with other object that linked to other concurrently running thread, but exchange message (or event) between each other. If we have no shared data we don't need any synchronization over data. Coming events to Active Object are keeping in event queue and will be sequentially processed by the object's event dispatcher without any blocking off or waiting for data, because the all needed data is owned the object. Below there is implementation of Active Object in Java. The implementation is illustrated by example "Dining Philosopher Problem" (DPP) such as C/C++ implementation from [1].
CLASS DIAGRAMM
![]() Active class has Hsm as a super class (see description of Hsm). Active implements Runnable interface from java.util package and AOEventListener interface. Philosopher and Table implement the concrete participants of DPP and both are a subclasses of Active class. Class DPP is an applet for visualization of the task and contains Philosopher and Table instances. Active CLASS The class has a fields:
Methods of the class:
![]() Fig.2. illustrates how events flows from event's producer to event's listener. Note the Active object always sends incoming events to its super class method
dispatchEvent(). Super class Hsm (state machine) implements a behavior of a core of Active object. Below there is brief description of Java implementation of DPP accordance with Active object concept. A core of DPP is a
Philosopher and Table classes and interactions between these. Note the Philosopher and Table have no shared data and communicate each other throw event exchange. The only critical for synchronization point is eventProcess( Event e ). Philosopher CLASS The Philosopher's state machine has a tree states: "hungry","thinking","eating".
Table CLASS The Table's state machine has a only "root" state. Table receives two events :
Moreover table has a inner class
ForkView designed for painting of a forks on applet canvas. DPP APPLET IN ACTION
LEGEND
14 February 2004
REFERENCES
1. Samek, Miro. Practical Statecharts in C/C++. Quantum Programming for Embedded Systems. CMP Books, 2002, ISBN: 1-57820-110-1
CODE
You can download source files or look over it below :
File Active.java
/*
* This is Active.java, created on Feb 4, 2004.
*/
package statemachine.hsm.achsm;
//import java.lang.Thread.State;
import java.util.*;
import statemachine.hsm.Event;
import statemachine.hsm.Hsm;
/**
* This class represents a Active Object of Hierarchical State Machine (HSM)
* @author Alexei Krasnopolski (krasnop@bellsouth.net)
*/
abstract public class Active extends Hsm implements Runnable, AOEventListener
{
static public boolean isDebug = false;
private Object lock = new Object();
private Thread thisThread; // reference to own thread of this active object
private Vector eventQueue; // queue of events incoming to the active object
private Vector listeners; // list og listeners - other active objects
// that wants to receive events from this active object
protected String objectName; // name of the object
public Active( String name )
{ // here is implicit invoking of Hsm() constructor
objectName = name;
thisThread = null; // while own thread not ready to run
eventQueue = new Vector(); // prepare queue for incoming events
listeners = new Vector();// prepare list for active objects to be
// registered as listeners
}
public void init()
{
super.init(); // invoke Hsm.init()
start(); // start own thread
}
public void run() // this is a body of the object's thread
{
Event e;
String debugString ="";
while( thisThread != null ) // it runs while reference to own thread is valid,
// if thisThread = null this thread will be stopped
{
try
{
synchronized( lock )
{
while( eventQueue.size() == 0 ) { // does the object have a unprocessed events?
log(null, " is waiting. " ); // debug message
lock.wait(); // if no events, pass control under processor to other thread
}
e = (Event)eventQueue.remove( 0 ); // yes, take one for dispatching to underlaying HSM
}
debugString = " {" + eventQueue.isEmpty() + " " + eventQueue.size() + "} event id= " + e.getID();
log(e, " dispatches event " ); // debug message
dispatchEvent( e );
}
catch(ArrayIndexOutOfBoundsException be )
{
System.out.println( " ------ ArrayIndexOutOfBoundsException with " + objectName
+ " queue=" + eventQueue.size() + debugString );
be.printStackTrace();
continue;
}
catch( InterruptedException ie )
{
System.out.println( "Thread of " + objectName +" was interrupted" );
}
}
}
public void start()
{
thisThread = new Thread( this, objectName ); // new own thread for this active object
thisThread.start();
if(isDebug)
System.out.println("start " + objectName ); // debug message
}
public void stop()
{
thisThread.interrupt(); // if thread is waiting for notification interrupt it
thisThread = null; // after that run() method for this object will be over.
if(isDebug)
System.out.println("stop " + objectName );
}
public void eventProcess( Event e )
{
log(e, " receives event from " + ((Active)e.getSource()).objectName ); // debug message
synchronized( lock )
{
eventQueue.add( e ); // add the event to event's queue ( don't need to lock
// the queue, Vector is thread safety )
log(e, " is notifyed..." ); // debug message
lock.notify();
}
}
public void eventPublish( Event e )
{
for(Iterator i = listeners.iterator(); i.hasNext(); ) // for all listeners from the list
{
((AOEventListener)i.next()).eventProcess( e ); // distribute the event
}
}
public void addAOEventListener( AOEventListener o )
{
listeners.add( o ); // add the listener to listener's list
}
protected synchronized void log(Event e, String msg) {
if(isDebug) {
String id = ((e == null)? "...":String.valueOf(e.getID()));
System.out.println( objectName + "[th=" + Thread.currentThread().getName() + "]"
+ msg + "[st=" + getState() + "; e.id=" + id + "]" ); // debug message
}
}
}
File AOEventListener.java
/*
* This is AOEventListener.java, created on Feb 4, 2004.
*/
package statemachine.hsm.achsm;
/**
* Interface for Active Object (AO) that wants to receive events from other AO
* @author Alexei Krasnopolski
*
*/
import java.util.EventListener;
public interface AOEventListener extends EventListener
{
public void eventProcess( statemachine.hsm.Event e );
}
File EventIDs.java
/*
* This is EventIDs.java, created on Feb 4, 2004.
*/
package dpp;
/**
* This is a collection of IDs for events created by Active Objects
* @author Alexei Krasnopolski (krasnop@bellsouth.net)
*/
public interface EventIDs
{
public static final int HUNGRY_SIG = 1;
public static final int DONE_SIG = 2;
public static final int EAT0_SIG = 3;
public static final int TIMEOUT_SIG = 100;
}
File Table.java
/*
* This is Table.java, created on Feb 4, 2004.
*/
package dpp;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Panel;
import statemachine.hsm.Event;
import statemachine.hsm.State;
import statemachine.hsm.achsm.Active;
/**
* Table class represents HSM and view for DPP's table
* (Table HSM is the same as in M.Samek "Practical Statecharts in C/C++")
* @author Alexei Krasnopolski (krasnop@bellsouth.net)
*/
public class Table extends Active implements EventIDs
{
final private boolean isDebug = false;
private boolean isForkFree[]; // control array for forks
private boolean isPhilosopherHungry[]; // control array for hungry philosopher
public Panel view; // view of the table with forks
private int N; // number of forks (and philosophers)
public static final int Rtable = 100; // table's radius
public static final int Hfork = 20; // size of a fork
class ForkView extends Panel // service view class
{
@Override
public void update( final Graphics g )
{ paint( g ); }
@Override
public void paint( final Graphics g )
{
g.setColor(getBackground()); // set bruch color
g.fillRect( 1, 1, Hfork - 2, Hfork - 2 ); // draw oval
g.setColor( Color.BLACK );
g.drawRect( 0, 0, Hfork - 1, Hfork - 1 );
super.paint(g);
}
public void changed( final boolean s )
{
setBackground( (s ? Color.white : Color.green) );
}
}
private ForkView forkView[]; // array of fork's views
public Table( final int phN )
{
super( "Table" );
N = phN;
isForkFree = new boolean[N];
isPhilosopherHungry = new boolean[N];
createView();
}
private void createView() // Layout fork's view on table's view
{
forkView = new ForkView[N];
view = new Panel()
{
@Override
public void update( final Graphics g )
{ paint( g ); }
@Override
public void paint( final Graphics g )
{
g.setColor( getForeground() );
g.fillOval( 1, 1, 2 * Rtable - 2, 2 * Rtable - 2 );
g.setColor( Color.BLACK );
g.drawOval( 0, 0, 2 * Rtable - 1, 2 * Rtable - 1 );
super.paint(g);
}
};
view.setSize( 2 * Rtable, 2 * Rtable );
view.setForeground( Color.lightGray );
view.setBackground( Color.white );
view.setLayout( null );
for( int i = 0; i < N; i++ )
{
forkView[i] = new ForkView();
forkView[i].setBackground( Color.white );
view.add( forkView[i] );
double angle = 2 * Math.PI / N * i - Math.PI / N;
int x = Rtable + (int)((Rtable - Hfork / 2 - 5) * Math.sin(angle)) - Hfork / 2;
int y = Rtable + (int)((Rtable - Hfork / 2 - 5) * Math.cos(angle)) - Hfork / 2;
forkView[i].setBounds( x, y, Hfork, Hfork );
}
}
private synchronized void debug()
{
if(!isDebug)
return;
System.out.print(" |");
for( int i = 0; i < N; i++ )
System.out.print( (isForkFree[i])? "F ":". " );
System.out.print("\n | ");
for( int i = 0; i < N; i++ )
System.out.print( ((isPhilosopherHungry[i])? "H ":"o ") );
System.out.print("\n | ");
for( int i = 0; i < N; i++ )
System.out.print( DPP.getPh()[i].getState().toString() + " " );
System.out.println();
}
@Override
public State fireInit()
{
for( int i = 0; i < N; i++)
{
isForkFree[i] = true;
isPhilosopherHungry[i] = false;
}
return null;
}
@Override
public void enter(){}
@Override
public State fireEvent(final Event e) // table has only state that is root node of HSM
{ int n, r, l, rr; // service variables
log(e," >>> fireEvent(). ");
debug();
n = ((Philosopher)e.getSource()).getNumber();// number of philosopher who sends this event
r = (n + 1) % N; // right neighbour (and his fork)
l = (n + N - 1) % N; // the left neighbour
switch( e.getID() )
{
case HUNGRY_SIG :
log(e, ": Philosopher<" + n + "> is hungry. " );
if( isForkFree[n] && isForkFree[r] ) // are both forks free?
{
forkView[r].changed( isForkFree[r] = false ); // now both forks are in duty
forkView[n].changed( isForkFree[n] = false ); // change views of the forks
view.repaint();
eventPublish( new Event( this, EAT0_SIG + n )); // post 'EAT' event for
// all philosophers (this is a little bit unefficial but flexible)
log(e,": Philosopher<" + n + "> is eating. {1}" );
}
else
{
isPhilosopherHungry[n] = true; // mark this philosopher as 'hungry'
}
break;
case DONE_SIG :
log(e, ": Philosopher " + n + " is thinking." );
forkView[r].changed( isForkFree[r] = true ); // free both forks
forkView[n].changed( isForkFree[n] = true );
if( isPhilosopherHungry[l] && isForkFree[l] ) // check left philosopher and his fork
{ // if philosopher is hungry and his fork is free :
forkView[n].changed( isForkFree[n] = false ); // set both forks busy
forkView[l].changed( isForkFree[l] = false );
isPhilosopherHungry[l] = false; // unmark the philosopher
eventPublish( new Event( this, EAT0_SIG + l ) ); // post 'EAT' event
log(e, ": Philosopher " + l + " is eating. {2}" );
}
rr = (r + 1) % N; // right fork of right philosopher
if( isPhilosopherHungry[r] && isForkFree[rr] ) // check both
{ // if he is hungry and left fork is free :
forkView[r].changed( isForkFree[r] = false ); // make both forks busy
forkView[rr].changed( isForkFree[rr] = false );
isPhilosopherHungry[r] = false; // unmark the philosopher
eventPublish( new Event( this, EAT0_SIG + r ) ); // post 'EAT' event
log(e, ": Philosopher " + r + " is eating. {3}" );
}
view.repaint();
break;
default :
break;
}
debug();
log(e, " <<< fireEvent(). ");
return getParent(); // always return null;
}
@Override
public void exit(){}
@Override
public String toString()
{
return "Root";
}
}
File Philosopher.java
/*
* This is Philosopher.java, created on Feb 4, 2004.
*/
package dpp;
import java.util.*;
import java.awt.Panel;
import java.awt.Color;
import java.awt.Graphics;
import statemachine.hsm.Event;
import statemachine.hsm.State;
import statemachine.hsm.achsm.Active;
/**
* Philosopher class represents HSM and view for DPP's philosopher
* (Philosopher HSM is the same as in M.Samek "Practical Statecharts in C/C++")
* @author Alexei Krasnopolski (krasnop@bellsouth.net)
*/
public class Philosopher extends Active implements EventIDs
{
private final int myNum; // personal number of Philosopher
/**
* Class field currentNum provides an unique number ids for instances of the class
*/
static private int currentNum;
Timer myTimer; // Personal timer or philosopher's watch
TimerTask eatingTimerTask; // task to be executed by personal timer
TimerTask thinkingTimerTask; // task to be executed by personal timer
public Panel view; // philosopher's view for drawing on applet panel
public static final int Rphil = 20; // philosopher radius
public static final int EATING_TIME = 4000; // msec
public static final int THINKING_TIME = 5000; // too
private Event timerEvent = new Event( Philosopher.this, TIMEOUT_SIG );
static
{
currentNum = 0; // static initializer for Philosopher's numbers
}
class MyTimerTask extends TimerTask
{
public void run()
{
eventProcess( timerEvent ); // when timer stops,
// 'TIMEOUT' event will be posted
}
}
public Philosopher()
{
super( "P<" + currentNum + ">" ); // create object name
myNum = currentNum++; // personal number of the Philosopher
myTimer = new Timer(); // create personal timer (watch)
view = new Panel() // create a view of Philosopher on an Applet's panel
{
public void update( Graphics g )
{ paint( g ); }
public void paint( Graphics g )
{
g.setColor(getForeground()); // set bruch color
g.fillOval( 1, 1, 2 * Rphil - 2, 2 * Rphil - 2 ); // draw oval
g.setColor( Color.BLACK ); // set black color of bruch
g.drawOval( 0, 0, 2 * Rphil - 1, 2 * Rphil - 1 ); // draw border
}
};
}
public void start()
{
super.start();
}
public void stop()
{
super.stop();
// myTimer.purge();
}
public int getNumber()
{
return myNum;
}
public void enter(){}
public State fireEvent( Event e )
{ return null; }
public State fireInit()
{ // root node of HSM makes initialization :
view.setBackground( Color.white ); // set white color of philosopher's view (thinking)
view.setSize( 2 * Rphil, 2 * Rphil );// set size
return hungry; // go to 'hungry' state
}
public void exit(){}
State thinking = new State( this ) // implementation of 'thinking' state of philosopher
{
public void enter()
{
log(null, " enters to thinking state. ");
try
{
myTimer.purge();
myTimer.schedule( // when philosopher enters to the state he starts timer
thinkingTimerTask = new MyTimerTask(),
THINKING_TIME + (int)(THINKING_TIME * Math.random())
);
}
catch( IllegalStateException e )
{
System.out.println( " ************ Ph #" + getNumber() + " --IllegalStateException, Thinking" );
}
catch( IllegalArgumentException e )
{
System.out.println( " ************ Ph #" + getNumber() + " --IllegalStateException, Thinking" );
}
view.setForeground( Color.white ); // set philosopher's color
view.repaint(); // send command to view's canvas
}
public void exit(){}
public State fireEvent( Event e )
{
switch( e.getID() )
{
case TIMEOUT_SIG : // we are here after philosopher's timer is stoped
log(e, " >>> fireEvent(). ");
transition( hungry ); // go to 'hungry' state
log(e, " <<< fireEvent(). ");
return null;
default :
break;
}
return null;
}
public State fireInit()
{ return null; }
public String toString()
{
return "T";
}
};
State hungry = new State( this ) // instantiation a 'hungry' state
{
public void enter()
{ // enter to the state and post 'HUNGRY' event for all listeners
log(null, " enter to hungry state. ");
eventPublish( new Event( Philosopher.this, HUNGRY_SIG ) );
view.setForeground( Color.red );
view.repaint();
}
public void exit(){}
public State fireEvent( Event e )
{
if( e.getID() == (EAT0_SIG + myNum) ) // check: is this 'EAT' event for me?
{
log(e, " >>> fireEvent(). ");
transition( eating ); // yes, it is for me: go to 'eating' state
log(e, " <<< fireEvent(). ");
}
return null;
}
public State fireInit()
{ return null; }
public String toString()
{
return "H";
}
};
State eating = new State( this ) // this is 'eating' state
{
public void enter() // enter to the state :
{
log(null, " enters to eating state. ");
try
{
myTimer.purge();
myTimer.schedule( // when philosopher enters to the state he starts timer
eatingTimerTask = new MyTimerTask(),
EATING_TIME + (int)(EATING_TIME * Math.random())
);
}
catch( IllegalStateException e )
{
System.out.println( " ************ Ph #" + getNumber() + " --IllegalStateException, Eating" );
}
catch( IllegalArgumentException e )
{
System.out.println( " ************ Ph #" + getNumber() + " --IllegalStateException, Eating" );
}
view.setForeground( Color.green );
view.repaint();
}
public void exit() // exit from the state :
{
eventPublish( new Event( Philosopher.this, DONE_SIG ) ); // post 'DONE' event
}
public State fireEvent( Event e )
{
switch( e.getID() )
{
case TIMEOUT_SIG :
log(e, " >>> fireEvent(). ");
transition( thinking ); // go to 'thinking' state
log(e, " <<< fireEvent(). ");
return null;
default :
break;
}
return null;
}
public State fireInit()
{ return null; }
public String toString()
{
return "E";
}
};
}
File DPP.java
/*
* This is DPP.java, created on Feb 14, 2004.
*
*/
package dpp;
import java.awt.Panel;
import java.awt.Color;
/**
* Class DPP represents a view of table with forks and philosophers for applet.
* @author Alexei Krasnopolski
*/
public class DPP extends Panel
{
static final int N = 5;
Table table = new Table( N );
static Philosopher philosopher[] = new Philosopher[N];
static Philosopher [] getPh()
{
return philosopher;
}
public void init()
{
setSize( 300, 300 );
setBackground( Color.white );
setLayout( null );
int rh = (int)(Philosopher.Rphil * Math.sqrt( 2 ));
for( int i = 0; i < N; i++)
{
philosopher[i] = new Philosopher();
add( philosopher[i].view );
double angle = 2 * Math.PI / N * i;
int r = Table.Rtable + rh;
int x = r + (int)(r * Math.sin(angle));
int y = r + (int)(r * Math.cos(angle));
philosopher[i].view.setLocation( x, y );
philosopher[i].addAOEventListener( table );
table.addAOEventListener( philosopher[i] );
philosopher[i].init();
}
add( table.view );
table.view.setLocation( Philosopher.Rphil + rh, Philosopher.Rphil + rh );
table.init();
System.out.println("init DPP ver. 0.0.3" );
}
public void start()
{
System.out.println("start DPP" );
for( int i = 0; i < N; i++)
{
philosopher[i].start();
}
table.start();
validate();
}
public void stop()
{
System.out.println("stop DPP" );
table.stop();
for( int i = 0; i < N; i++)
{
philosopher[i].stop();
}
}
}
File DPPApplet.java
/*
* This is DPPApplet.java, created on Feb 7, 2004.
*/
package dpp;
import java.applet.*;
/**
* @author Alexei Krasnopolski
*/
public class DPPApplet extends Applet
{
static DPP dppView = null;
public void init()
{
System.out.println("init applet" );
if( dppView == null )
{
dppView = new DPP();
dppView.init();
}
add( dppView );
setLayout( null );
setSize( 300, 300 );
}
public void start()
{
System.out.println("start applet" );
if( dppView != null )
{
dppView.start();
dppView.validate();
}
}
public void stop()
{
System.out.println("stop applet" );
if( dppView != null )
{
dppView.stop();
}
}
}
|






