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(); } } } |