However, there is often the need for further parallelism within each agent because an agent may be involved in negotiations with other agents and each negotiation should proceed at its own pace. We could use additional Threads to handle each concurrent agent activity but this becomes very inefficient because Java Threads (in spite of the light-weight connotation of the name) were not designed for large-scale parallelism. Rather, they were designed to allow Java programs to exploit the real parallelism of multi-processor architectures and, in current Java releases, each Java Threads requires one OS Thread. This means that passing control from one Thread to another, is about 100 times slower than simply calling a method.
In order to support efficiently parallel activities within an agent, Jade has introduced a concept called Behaviour [US readers take note: Behaviour, not Behavior].
A behaviour is basically an Event Handler, a method which describes how an agent reacts to an event. Formally, an event is a relevant change of state; in practical terms, this means: reception of a message or a Timer interrupt. In Jade, Behaviours are classes and the Event Handler code is placed in a method called action.
Although the use of Behaviours promotes efficiency, it doesn't simplify programming. Consider coding the steps in a negotiation: sending offers, waiting for counter-offers and finally reaching agreement. This activity consists of an alternation of active phases - when the agent decides what to do and sends messages - and passive phases - when the agent waits for an answer. Threads can pause in the middle of execution to wait for messages and continue without losing context. So if we use Threads, the sequence of activities maps directly into sequences of instructions. Not so when we use Behaviours.
Behaviour actions are methods, executed one after the other by the agent's Thread after events. Like listeners in graphic interfaces, they cannot pause without blocking all other activity [within the agent]. So this is the important thing to remember about Behaviours is that:
To implement long-term activities like a negotiation, we have to provide as many different Behaviours as there are active phases in the activity. We must also arrange for them to be created and triggered in the right sequence. Actually, in Jade, as our examples will show, this isn't very hard to do.
public class Agent1 extends Agent
{
protected void setup()
{
addBehaviour( new Looper( this, 300 ) );
addBehaviour( new Looper( this, 500 ) );
}
}
The parameters (300 and 500) mean that the first Looper should print a line every 300 ms and the second every 500 ms.
The Looper behaviour is described in a seperate file. This behaviour prints out a message with the elapsed time and the agent's name every dt milliseconds, where dt is the parameter. As shown below, the action method uses a new primitive block( <delay in msec> ) which takes the behaviour out of the active queue and starts a timer to make it active again after the prescribed delay [Note: the behaviour would also be reactivated if a message were received or the agent restarted].
public void action()
{
System.out.println( tab +
(System.currentTimeMillis()-t0)/10*10 + ": " +
myAgent.getLocalName() );
block( dt );
n++;
}
As in our previous example, the done() method in Looper.java terminates after 6 executions. The rest of the code deals with formatting the trace with a timestamp.
Below we show the output from running Agent1. To clarify the trace, we've removed Jade's standard messages: version and Main-Container address:
jean% java jade.Boot aaa:Agent1
0: aaa
0: aaa
300: aaa
510: aaa
620: aaa
920: aaa
1010: aaa
1220: aaa
1520: aaa
1530: aaa
2020: aaa
2530: aaa
The output clearly show the interleaving of the active phases of the two behaviours. The timestamps also show that, due to overhead, delays are not always exact.
The parallel behaviour is even more apparent if we start two Agent1 agents, 'aa' and 'zzzzz'.
jean% java jade.Boot aa:Agent1 zzzzz:Agent1
0: zzzzz
0: aa
10: zzzzz
10: aa
300: zzzzz
310: aa
510: zzzzz
510: aa
610: zzzzz
610: aa
910: zzzzz
920: aa
1020: zzzzz
1020: aa
1220: zzzzz
1220: aa
1520: zzzzz
1520: aa
1520: zzzzz
1530: aa
2030: zzzzz
2030: aa
2530: zzzzz
2530: aa
public class Bad1 extends Agent
{
protected void setup()
{
addBehaviour( new TwoStep() );
addBehaviour( new Looper( this, 300 ) );
}
}
class TwoStep extends SimpleBehaviour
{
public void action()
{
block(250);
System.out.println( "--- Message 1 --- " );
block(500);
System.out.println( " - message 2 " );
finished = true;
}
private boolean finished = false;
public boolean done() { return finished; }
}
And this is the output:
jean% java jade.Boot john:Bad1
--- Message 1 ---
- message 2
10: john
310: john
620: john
920: john
1220: john
1530: john
Jade beginners often think that block is the equivalent of sleep. Therefore they are surprised that the two messages are printed without delay within the first 10 msec. The explanation is that:
| block(dt) doesn't block; it just sets a delay for the next execution of the behaviour |
In our example, the action method was executed completely right after setup() and the two messages were printed. The calls to block meant that the next execution of the behaviour was scheduled for some time in the future; but because finished was set to true, the behaviour never got to execute a second time.
class BlockTwice extends SimpleBehaviour
{
static long t0 = System.currentTimeMillis();
public void action()
{
System.out.println( "Start: "
+ (System.currentTimeMillis()-t0) );
block(250);
System.out.println( " after block(250): "
+ (System.currentTimeMillis()-t0) );
block(1000);
System.out.println( " after block(1000): "
+ (System.currentTimeMillis()-t0) );
System.out.println();
}
private int n = 0;
public boolean done() { return ++n > 3; }
}
Here are the results:
jean% java jade.Boot tom:Block2
Start: 1
after block(250): 7
after block(1000): 9
Start: 258
after block(250): 261
after block(1000): 263
Start: 512
after block(250): 515
after block(1000): 517
Start: 767
after block(250): 769
after block(1000): 771
As we previously noted, the calls to block(..) don't introduce any delay between the statements in the action method. Furthermore, successive behaviour executions occur 250 msec apart and we conclude that only the first invocation of block is significant. Further attemps to block a blocked behaviour have no effect.
class TwoStep extends SimpleBehaviour
{
public void action()
{
try
{ System.out.println( "--- TwoStep start: " + ...time );
Thread.sleep(200);
System.out.println( " -- Message 1 ---: " + ...time );
Thread.sleep(500);
System.out.println( " - message 2 : " + ...time );
}
catch (Exception e) {}
}
private int n = 0;
public boolean done() { return ++n > 2; }
}
and the output:
jean% java jade.Boot mary:Bad3
--- TwoStep start: 20
-- Message 1 ---: 220
- message 2 : 720
700: mary
--- TwoStep start: 730
-- Message 1 ---: 930
- message 2 : 1430
--- TwoStep start: 1440
-- Message 1 ---: 1640
- message 2 : 2140
2120: mary
2430: mary
2730: mary
3040: mary
3340: mary
Now the sequential actions are timed properly: message1 is printed 200 msec after the start of the behaviour and the second message 0.5 sec after that; BUT there has been no interleaving of the cyclic Looper behaviour with the TwoStep behaviour. The cyclic Looper should be activated every 300 msec, printing at times: 0, 300, 600, 900 etc.... Instead its first execution is at 700 and the second 1400 msec after that.
Here is the code for Step1, the first behaviour which waits 200 msec then prints out Msg1 and creates Step2, the behaviour which will print the second message and terminate the activity of the agent.
class Step1 extends SimpleBehaviour
{
int state = 0;
public void action()
{
if (state==0) block( 200 );
else if (state==1)
{
System.out.println( "--- Message 1 --- " );
addBehaviour( new Step2() );
}
state++;
}
public boolean done() { return state > 1; }
}
The pattern is simple. The state variable counts the number of times we have executed the behaviour. On the first execution, we use block to schedule the next execution 200 msec later. The next time around, we print out Message1 and create the Step2 Behaviour whose structure will be very similar to this one. In this example, Step1 is a class local to the agent so that the agent method addBehaviour() can be used directly. If this behaviour were compiled seperately we would have to write something like "myAgent.addBehaviour()". After the second execution, we have no further use for the Behaviour and we arrange for done() to return true.
Here is the code for the second behaviour:
class Step2 extends SimpleBehaviour
{
int state = 0;
public void action()
{
if (state==0)
block( 600 );
else {
System.out.println( " - message 2 " );
doDelete(); // applies to the Agent
}
state++;
}
public boolean done() { return state>1; }
}
The major difference is in the use of a new (Agent) method doDelete() which removes the agent from the system and terminates all its active behaviours. The complete code is given in Agent2.java and the output is shown below. Note that the Looper behaviour is interleaved with the sequential message printing but that all activity stops after the agent has been deleted.
jean% java jade.Boot harry:Agent2
0: harry
--- Message 1 ---
340: harry
640: harry
- message 2
class TwoSteps extends SimpleBehaviour
{
int state = 1;
public void action()
{
switch( state ) {
case 1:
block( 200 );
break;
case 2:
System.out.println( "--- Message 1 --- " );
block( 800 );
break;
case 3:
System.out.println( " -- message 2 --" );
finished = true;
doDelete(); // applies to the Agent
}
state++;
}
private boolean finished = false;
public boolean done() { return finished; }
}
The output is shown below:
jean% java jade.Boot alice:Agent3
0: alice
--- Message 1 ---
340: alice
640: alice
950: alice
-- message 2 --
jean%
Since the sequential part ends after 1.0 sec, we now get an extra line from the Loop behaviour.
You will notice that the program ends normally and we get back to the shell prompt without having to do a CTL-C. To get this behaviour, we provided the agent with a takeDown() method. This is called automatically when an agent is deleted. Most often, this is where an agent removes its entries from various directories; but, in our case, we stopped the container and terminate the program with "System.exit(0)". The whole program is given in Agent3.java.
updated feb. 23, 2004
- Top
- Previous
- Next