Programming Java threads in the real world, Part 9
More threads in an object-oriented world: Synchronous dispatchers, active objects, detangling console I/O
By Allen Holub, JavaWorld.com, 06/01/99
Page 4 of 6
The problem of dispatching is essentially the same problem that I discussed in the context of Observer notifications (in the
March 1999 column). I don't want to synchronize dispatch(...) (Listing 1, line 56) because I don't want to disallow the addition of new operations while dispatching is in progress. Here, I've taken the easy
way out and copied the list inside the synchronized statement (Listing 1, line 67). A multicaster-based solution, as discussed in the March column, could also work.
The metered_dispatch(...) (Listing 1, line 79) variant on dispatch just uses an Alarm (discussed in February 1999) to dispatch events at a fixed interval.
Listing 1: /src/com/holub/asynch/Synchronous_dispatcher.java
|
|
package com.holub.asynch;
.
import java.util.*;
import com.holub.asynch.Alarm;
|
|
/***********************************************************************
| |
| |
|
A synchronous notification dispatcher executes a sequence of operations sequentially. This allows two sets of linked operations
to be interspersed and effectively executed in parallel, but without using multiple threads. This class is built on the JDK
1.2x LinkedList class, which must be present in the system.
|
(c) 1999, Allen I. Holub.This code may not be distributed by yourself except in binary form, incorporated into a Java .class file. You may use this
code freely for personal purposes, but you may not incorporate it into any commercial product without my express permission
in writing. |
@author Allen I. Holub
|
|
|
|
|
|
|
| |
| |
|
Add a new handler to the end of the current list of subscribers.
|
|
|
|
|
|
| |
| |
|
Add several listeners to the dispatcher, distributing them as evenly as possible with respect to the current list.
|
|
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
|
public synchronized void add_handler( Runnable[] handlers )
{
if( events.size() == 0 )
{ for( int i=0; i < handlers.length; )
events.add( handlers[i++] );
}
else
{ Object[] larger = events.toArray();
Object[] smaller = handlers;
if( larger.length < smaller.length )
{ Object[] tmp = larger;
larger = smaller;
smaller = tmp;
}
int distribution = larger.length / smaller.length;
LinkedList new_list = new LinkedList();
int large_source = 0;
int small_source = 0;
while( small_source < smaller.length )
{ for( int skip = 0; skip < distribution; ++skip )
new_list.add( larger[large_source++] );
new_list.add( smaller[small_source++] );
}
events = new_list;
}
}
|
|
/*******************************************************************
| |
| |
|
Remove all handlers from the current dispatcher.
|
|
|
|
|
|
| |
| |
|
Dispatch the actions "iterations" times. Use -1 for "forever." This function is not synchronized so that the list of events
can be modified while the dispatcher is running. The method makes a clone of the event list and then executes from the clone
on each iteration through the list of subscribers. Events added to the list will be executed starting with the next iteration.
|
|
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
|
public void dispatch( int iterations )
{
// Dispatch operations. A simple copy-and-dispatch-from-copy
// strategy is used, here. Eventually, I'll replace this code
// with a <code>Multicaster</code>.
if( events.size() > 0 )
while( iterations==-1 || --iterations >= 0 )
{
Object[] snapshot;
synchronized( this )
{ snapshot = events.toArray();
}
for( int i = 0; i < snapshot.length; ++i )
{ ((Runnable)snapshot[i]).run();
Thread.currentThread().yield();
}
}
}
|
|
| |
| |
|
Dispatch actions "iterations" number of times, with an action dispatched every "interval" milliseconds. Note that the last
action executed takes up the entire time slot, even if the run() function itself doesn't take "interval" milliseconds to execute.
Also note that the timing will be irregular if any run() method executes in more than "interval" milliseconds. If you want
a time interval between iterations, but not between the operations performed in a single iteration, just insert a Runnable
action that sleeps for a fixed number of milliseconds. @param iterations`number of times to loop through the actions executing them. Use -1 to mean "forever." #param interval An action is executed
every "interval" milliseconds.
|
|
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
public void metered_dispatch( int iterations, int interval )
{
Alarm timer = new Alarm( interval, Alarm.MULTI_SHOT );
timer.start();
while( iterations==-1 || --iterations >= 0 )
{
Object[] snapshot;
synchronized( this )
{ snapshot = events.toArray();
}
for( int i = 0; i < snapshot.length; ++i )
{ ((Runnable)snapshot[i]).run();
timer.await();
timer.start();
}
}
timer.stop();
}
static public class Test
{
// Execute the test with:
// java "com.holub.asynch.Synchronous_dispatcher\$Test"
//
public static void main( String[] args )
{
Synchronous_dispatcher dispatcher =
new Synchronous_dispatcher();
dispatcher.add_handler(
new Runnable()
{ public void run()
{ System.out.print("hello");
}
}
);
dispatcher.add_handler(
new Runnable()
{ public void run()
{ System.out.println(" world");
}
}
);
dispatcher.dispatch( 1 );
dispatcher.metered_dispatch( 2, 1000 );
//------------------------------------------------
// Test two tasks, passed to the dispatcher as arrays
// of chunks. Should print:
// Hello (Bonjour) world (monde)
Runnable[] first_task =
{ new Runnable(){ public void run(){ System.out.print("Hello"); }},
new Runnable(){ public void run(){ System.out.print(" World");}}
};
Runnable[] second_task =
{ new Runnable(){ public void run(){ System.out.print(" Bonjour");}},
new Runnable(){ public void run(){ System.out.print(" Monde" );}}
};
dispatcher = new Synchronous_dispatcher();
dispatcher.add_handler( first_task );
dispatcher.add_handler( second_task );
dispatcher.dispatch( 1 );
}
}
}
|
Active objects
The second architecture I'd like to discuss this month is the active object architecure. Though we can thank Greg Lavender and Doug Schmidt for the name (see Resources), the architecture has been around for a while -- the first time I saw it (dinosaur that I am) was in Intel's RMX operating
system, circa 1979.