Managing Multitasking.................................................................................................

CREATING THREADS..................................................................................................................

The Runnable Interface..............................................................................................................

The Start and Run methods.....................................................................................................

Thread Control..........................................................................................................................

Thread Attributes......................................................................................................................

WHEN DO APPLET THREADS RUN?.....................................................................................

SHARED RESOURCES AND SYNCHRONIZATION...........................................................

Efficient Serialization..............................................................................................................

SUBTERRANEAN THREADS...................................................................................................

INTER-PROCESS COMMUNICATION..................................................................................

INTER-THREAD COMMUNICATION...................................................................................

INTER-APPLET COMMUNICATION.....................................................................................

WHEN TO THREAD....................................................................................................................

ANIMATION.................................................................................................................................

Using Non-Daemon Threads...................................................................................................

NATIVE METHODS....................................................................................................................

CONCLUSION...............................................................................................................................

EXERCISES...................................................................................................................................

 


Managing Multitasking

For years, multiprocessing systems have allowed you to run multiple processes (programs) simultaneously. More recently, many operating systems have begun to support multithreading--allowing programmers to spawn multiple sub-processes within each process.

Threads differ significantly from processes. In a non-multithreaded system, there are only processes and related processes. Related processes share only open file handles. In a multithreaded system, each process is made up of from 1 to n threads and all the threads in the process generally share global variables as well as open file handles. This sharing of global variables makes it very easy for threads to share work product, while remaining single-mindedly dedicated to their assigned task.

To get a better handle on the shift from singlethreaded to multithreaded programming, picture a company where every employee has his own building (complete with telephone system, bathroom, cafeteria, office supplies and parking lot). Each person can do his job, as long as everything he needs is in his building, a condition the company goes to great expense to try to ensure. Any job that requires access to another employee's work product is immensely complicated and requires a considerable amount of communication. If the boss wants something done, he has to pick up the phone. This is the one-process/one-thread model. Now put all those employees into a single building. You immediately get less cumbersome communication and more efficient resource utilization. This is the one-process/multi-thread model.

Java is an inherently multithreaded language. As such, it depends on operating system support for threads and multitasking. This is one of the reasons that Java has not, as of this writing, been ported to the immensely popular Windows 3.1 operating environment. In fact, to see just how multithreaded Java is, we need only look at Object, the base class for all Java classes. Six of Object's twelve public methods involve thread control and interthread communication.

Creating Threads

Java encapsulates the notion of a thread in a class named, appropriately enough, Thread. Table 6.1 gives the methods for the Thread class. It incorporates most of the thread attributes and control methods available to programmers who write to the native operating system thread control API.

Table 6.1: Methods of the Thread class.

Method Description

public Thread() Create a new Thread.  Underlying operating system thread IS NOT CREATED UNTIL Thread.start is called. (this applies to all Thread constructors)

public Thread(String name)      Create a new Thread with the specified name.
public Thread(ThreadGroup group, String name)         Create a new Thread with the specified name and add it to the specified ThreadGroup.
public Thread(Runnable target)            Create a new Thread from an object implementing the Runnable interface.

public Thread(ThreadGroup group, Runnable target)   Create a new Thread from an object implementing the Runnable interface and add it to the specified ThreadGroup.

public Thread(Runnable target, String name)   Construct a new Thread from an object implementing the Runnable interface and set its name to name.
public Thread(ThreadGroup group, Runnable target, String name )     Same as above, but adds Thread to the specified ThreadGroup.

public static int activeCount()   Returns the number of active threads in this thread group.  Does not include subterannean threads.

public int countStackFrames()  Returns the number of stack frames in this thread. The thread must be suspended for this to work. Throws IllegalStateException if thread is not suspended.
public static Thread currentThread()     Returns the currently executing Thread Object.

public void destroy()    Kills the thread without cleanup.  The equivalent of UNIX's "kill -9" for Java threads.
public static void dumpStack() Dumps the stack for the current Thread.
public static int enumerate( Thread tarray[]) - Copies references to every active Thread into the supplied Thread array. Returns the number of Threads in the array.
public String getName()           Returns the thread's name, i.e. whatever name was assigned by setName. If no name has been set via setName, Java assigns a name of the form "Thread-N" where N indicates that this is the Nth thread to be created in this thread group.

public int getPriority()  Returns the thread's priority. This will be a value between Thread.MIN_PRIORITY and Thread.MAX_PRIORITY.
public boolean isAlive()           Returns true if the underlying operating system thread is executing, i.e. if start has been called successfully and stop has not been called yet. Also returns true if a running thread has been suspended.  Equivalent to asking if Thread.run is still executing.
public boolean isDaemon()      Returns true if this is a daemon thread, i.e. if a successful call to setDaemon(true) has been made.
public synchronized void join(int num_millisecs)        Waits for num_millisecs milliseconds for this thread to die. Waits forever if num_millisecs is 0.

public synchronized void join()            Waits forever for this thread to die. Do not call join on your own thread. Always use in a construction such as: Thread x = new Thread(); x.start(); x.join().
 public void resume()   Resume execution of a suspended thread. Throws IllegalStateException if thread was not suspended.
public void run()          Override this method with the main loop for your new thread. This is the method that runs in the new operating system thread.
public void setDaemon(boolean bDaemon)      Sets the threads daemon status to bDaemon. If true, Thread becomes a daemon thread.  Must be called before the thread becomes active, i.e., before any call to Thread.start.

public void setName(String newName) Set the thread's name to newName.  Can be called at any point in the thread's life.

public void setPriority(int newPriority) Set the threads priority to newPriority. Throws IllegalArgumentException if newPriority is not within the range Thread.MIN_PRIORITY to Thread.MAX_PRIORITY.
public static void sleep(int millis)        The current thread will pause for millis milliseconds.
public synchronized void start()           Causes Java to create a new operating system thread and begin running the run method in the new thread. Returns immediately, usually before the thread has begun execution.
public synchronized void stop()           Stops a Thread by throwing a ThreadDeath object. If the Thread has not started, it will be killed immediately rather than waiting for it to start.
public void suspend()  Suspends the thread. Call resume to restart the thread.

public String toString()            Returns a String that includes the thread name, priority and thread group.
public static void yield()           Yields this thread's time slice to the next thread waiting to execute. Has no effect if there are no threads available to execute.

There are two types of Thread constructors that correspond with the two different ways of getting an object to run in its own thread. The first, most obvious, is to subclass the Thread class. This is entirely reasonable and the method we use with the AgentDispatcher which subclasses Thread, and creates its own Socket. We could have done it the other way around, subclassing Socket and creating a Thread, but as the saying goes, “you make your choices and take your chances.” Listing 6.1 demonstrates how AgentDispatcher extends the Thread class so that it will run in its own thread.

Listing 6.1: Extending the Thread class.

 

/** There is one AgentDispatcher active at any one time.  It

maintains a CONSTANT connection from the AgentLauncher to the

Dispatching AgentServer.

@version 1.0

@author John Rodley 12/1/1995

*/

class AgentDispatcher extends Thread {

     Socket s;

     public AgentDispatcher( String ServerName, int port ) {

    setDaemon(true);

           try {

                s = new Socket( InetAddress.getByName(ServerName),port );

                } catch( IOException e )

        {System.out.println("exception "+e ); }

           }

 

/** Dispatch a kill message to all servers telling them to

terminate the named Agent with prejudice.

*/

     public void StopAgent(String id) {

           KillMessage km = new KillMessage(id);

           try { s.getOutputStream().write( km.getMessageBytes()); }

           catch( IOException e )

        { System.out.println( "stop output exception "+e); }

           try { s.close(); }

           catch( IOException e1 )

        { System.out.println( "stop close exception "+e1); }

           }

 

/** Tell the dispatching agent server to dispatch this agent.

*/

     public void Dispatch( String Name, String ID, Vector Args ) {

           DispatchMessage dm = new DispatchMessage(Name,ID,Args);

           try { s.getOutputStream().write( dm.getMessageBytes()); }

           catch( IOException e )

       { System.out.println( "dispatch out exception "+e); }

           }

 

/** The main run loop for this thread.  Sits in a loop reading

the socket and processing messages.  The only messages that

should come in over this socket are StartMessage and

ResultsMessage.

*/

     public void run() {

           byte buffer[] = new byte[8192];

           while( true ) {

                try {

                int ret;

                if(( ret = s.getInputStream().read( buffer )) != 0 )

                     {

                     if( ret < 0 )      {

                           System.out.println( "connection lost");

                           break;

                           }

                     ClientProcess( buffer, ret );

                     }

                } catch( IOException e ) {

                     System.out.println( "IOexception "+e );

                     break;

                     }

                }

           try{s.close();}

    catch(Exception e)

        { System.out.println("run close exception "+e);}

           }

 

/** Parse a message from the dispatching agent server.  Only

valid message types are Start and Results.  Take appropriate

action for each message type, updating the AgentLauncher

display.  

*/

  public void ClientProcess( byte b[], int numbytes ) {

 

  String command;

  int currentOffset = 0;

  String s;

  int messageStart;

  boolean bret = false;

 

  while( currentOffset != numbytes ) {

     messageStart = currentOffset;

       // Every message starts with 4 bytes of command

     command = new String( b, 0, currentOffset, 4 );

       currentOffset += LoadMessage.PREFIX_SIZE;

 

     // Followed by 10 bytes ascii length of the whole message

       // including command

     String sLength = new String( b, 0, currentOffset, 10 );

       currentOffset += LoadMessage.LOADLEN_SIZE;

    

     if( command.compareTo(ResultMessage.RESULT_PREFIX ) == 0 )

          {

             ResultMessage rm = new ResultMessage();

           rm.parse( b, currentOffset );

          AgentLauncher.currentAgentLauncher.reportResult(

             rm.server, rm.theURL);

             }

     else

          {

             if( command.compareTo(StartMessage.START_PREFIX ) == 0 )

                  {

                StartMessage sm = new StartMessage();

                sm.parse( b, currentOffset );

                AgentLauncher.currentAgentLauncher.addAgentFace(sm.server);

                  }

           else {

                System.out.println( "Message is BOGUS "+b);

        return;

        }

           }

       Integer il = new Integer( sLength );

     currentOffset = messageStart + il.intValue();

       }  // while bytesused

     }

}

 

The theory behind the AgentDispatcher is simple and common to most threaded I/O.  The AgentDispatcher needs to handle both sides of the AgentLauncher<->dispatching AgentServer conversation.  We want messages from the AgentServer to the AgentLauncher to be handled asynchronously as they occur so that, for instance, a results message from a particular server will cause the AgentLauncher to change the display for that server as soon as it’s received.

In order to achieve that, we write a run method that is a simple infinite loop that reads the Socket and processes whatever it receives by making calls back into the AgentLauncher.  We’ll talk more about the details of run methods later, but the key thing to realize here is that the run method is the top-level method of the new operating system thread, and that within the run method, the call to InputStream.read blocks.  That is, the call doesn’t return until it has read some data over the Socket.  Thus, given the sporadic nature of our communications, the new operating system thread spends most of its time sleeping.

There are only two direct references to Threads in the AgentDispatcher.  The first one is in the declaration itself where we declare that AgentDispatcher extends Thread.  Then, within the constructor, we set the Thread to be a daemon Thread.  Daemon Threads are discussed at more length later in this chapter.

Listing 6.2: The AgentLauncher creating and starting an AgentDispatcher.

/** Dispatch the current Agent.  currentAgent is an instance of

the Agent we wish to dispatch, and he has already configured

himself via Agent.configure.  We need to get his arguments as a

Vector of Strings, create a dispatcher, then tell the

dispatcher to dispatch using the currentAgent with those

arguments.

@see Agent

@see AgentDispatcher

*/

public void Dispatch() {

     Vector v = currentAgent.getArguments();

     currentDispatcher =

      new AgentDispatcher( disServerName, disServerPort );

     currentDispatcher.start();

     currentDispatcher.Dispatch( currentClassName, currentID, v );

     changeAppState( AGENT_DISPATCHED );

     }

Remember that AgentLauncher is our top-level object, our Applet, and AgentLauncher.Dispatch is invoked when the user presses the dispatch button.  Within Dispatch we instantiate AgentDispatcher, which gives us, via subclassing, a Thread object.  Then we call Thread.start to get the actual operating system thread running.  The invocation of Thread.start is the key call in the whole thread creation process.  Without it, there will not be a new operating system thread.

Though there are only those two direct references to Threads in the AgentDispatcher, it is nonetheless a good example of a threaded object because it encapsulates some functionality that executes within the new operating system thread, and some that does not.  In this example, AgentLauncher.Dispatch, the AgentDispatcher constructor, AgentDispatcher.Dispatch, and AgentDispatcher.StopAgent all run in one thread, while AgentDispatcher.run, AgentDispatcher.ClientProcess run in another.  Later on, when we discuss the start and run methods, we’ll find out how to tell which methods execute in which threads.

The Runnable Interface

In a multiple-inheritance situation, the subclassing constructor is all you'd need because all runnable objects could simply inherit Thread with all their other super-classes. But Java doesn't allow multiple inheritance, and it doesn't seem reasonable to create a long inheritance branch (with Thread up near the root) simply to make something runnable.

Fortunately, there's a second style of Thread constructor. This one takes an object which implements the Runnable interface as one of its parameters. This is what we use with our AgentLauncher applet which, in its start method, creates a Thread and passes itself as the parameter to the Thread constructor. This constructor was specifically designed to deal with situations where you don't want to subclass Thread.

The Runnable interface has only a single method: run. The run method contains the main loop of a Runnable object. Our AgentDispatcher's run method, for example, contains a loop that simply reads a socket.

Thus we have two classes, AgentLauncher and AgentConnectionHandler, that run in their own thread but get there by different routes. AgentLauncher is a subclass of Thread, while AgentConnectionHandler is a subclass of Object which implements the Runnable interface. Listing 6.3 shows the parts of the AgentLauncher that relate to its use of threads and the Runnable interface.

Listing 6.3: The AgentLauncher implementing the Runnable interface.

public final class AgentLauncher extends Applet implements Runnable {

 

/** The main thread */

Thread myThread = null;

 

/** start - Whenever a user visits our page, Applet calls

this.  This is an override of an Applet method.  Creates a

thread and passes this AgentLauncher into it.  Works because

we implement the Runnable method. */

  public void start() {

     currentAgentLauncher = this;

     if( myThread == null ) 

          {

           myThread = new Thread( this );

           myThread.setName( "AgentLauncher" );

          myThread.start();

           }

    }

We remember from our discussion of Applets in Chapter 4, that Applet.start is called whenever the user “visits” the page containing this applet. When we come into start for the very first time, we want to get our AgentLauncher’s run method executing in a new operating system thread.  In order to do that, we use a new style of Thread constructor, one which takes a Runnable object as one of its arguments.  In Listing 6.3, the AgentLauncher itself (this in the start method) is the Runnable object. When, in AgentLauncher.start, we call myThread.start, we get the exact same effect as we got in Listing 6.1 - the run method of our AgentLauncher executes in a new operating system thread.

The Start and Run methods

No matter which type of Thread you create, it's important to realize just what methods execute in which thread. Listing 6.4 shows the creation of an AgentDispatcher and shows which thread each line of code executes in. When I think of a Thread object, what immediately comes to mind is a thread which begins execution with the constructor of the object, and terminates after the destructor returns. This is most emphatically not the case with Java Thread objects (or almost any objectified threads). The Thread constructor and the start method both execute in the thread FROM which they were invoked. Only the run method executes in the new thread. Of course, this means that any method invoked from within the run method will also execute in the new thread.

Listing 6.3: Using operating system threads.

Thread     Source

public void Dispatch() {

1   Vector v = currentAgent.getArguments();

1   currentDispatcher =

      new AgentDispatcher( disServerName, disServerPort );

1   currentDispatcher.start();

1   currentDispatcher.Dispatch( currentClassName, currentID, v );

1   changeAppState( AGENT_DISPATCHED );

     }

 

class AgentDispatcher extends Thread {

     Socket s;

1   public AgentDispatcher( String ServerName, int port ) {

1    setDaemon(true);

1         try {

1              s = new Socket( InetAddress.getByName(ServerName),port );

1              } catch( IOException e )

1        {System.out.println("exception "+e ); }

1         }

 

     public void StopAgent(String id) {

?         KillMessage km = new KillMessage(id);

?         try { s.getOutputStream().write( km.getMessageBytes()); }

?         catch( IOException e )

?        { System.out.println( "stop output exception "+e); }

?         try { s.close(); }

?         catch( IOException e1 )

?        { System.out.println( "stop close exception "+e1); }

           }

 

     public void Dispatch( String Name, String ID, Vector Args ) {

?         DispatchMessage dm = new DispatchMessage(Name,ID,Args);

?         try { s.getOutputStream().write( dm.getMessageBytes()); }

?         catch( IOException e )

?       { System.out.println( "dispatch out exception "+e); }

           }

 

     public void run() {

2         byte buffer[] = new byte[8192];

2         while( true ) {

2              try {

2              int ret;

2              if(( ret = s.getInputStream().read( buffer )) != 0 )

                     {

2                   if( ret < 0 )      {

2                         System.out.println( "connection lost");

2                         break;

                           }

2                   ClientProcess( buffer, ret );

                     }

2              } catch( IOException e ) {

2                   System.out.println( "IOexception "+e );

2                   break;

                     }

                }

2         try{s.close();}

2    catch(Exception e)

2        { System.out.println("run close exception "+e);}

           }

 

public void ClientProcess( byte b[], int numbytes ) {

2          All code excecutes in 2

     }

}

 

In this example, AgentLauncher.Dispatch starts off running in thread-1.  It then creates an AgentDispatcher, and starts it running in its own thread via Thread.start.  Thread.start creates the new thread, thread-2, and starts it running our run method.  All the code in run executes in thread-2.  As well, every method invoked from run also executes in thread-2, so ClientProcess all happens in thread-2.  Notice that, in this example, we can’t specify which thread StopAgent and Dispatch run in.  If the user presses the Kill or Dispatch buttons, then these methods are invoked from thread-1, and thus they themselves run in thread-1.  They might, however, be invoked in response to a message from the AgentServer.  In that case, the message comes over the Socket and is read in the run method which executes in thread-2.  The message is passed to ClientProcess, still in thread-2, and anything invoked by ClientProcess runs in thread-2. 

Though we call the whole class Thread, you should really think of the run method and the underlying operating system thread as one and the same. The first line of the run method is the first line of Java code executed by the new thread, and the return from the run method is the last line of Java code executed by the new thread. This is why every run method you see will be some sort of loop. In essence, the run method is the equivalent of main() in a single-threaded C program. Everything that happens in the new thread starts within the run method. (Conversely, if you can't trace an instruction back to somewhere within the run method, it didn't happen in that thread!). When the run method returns, the thread disappears from the system. In fact, run never really returns. Since it executes independently, it has nowhere to return to.

The run method is never explicitly called. Instead, we invoke the start method. This causes Java to create a new operating system thread that executes the run method. One of the most frustrating dumb mistakes you can make in Java is to create a Thread, then forget to invoke Thread.start(). Gee, I wonder why my thread isn't running?  Another dumb mistake (yes, I’ve made it) is to invoke run rather than start.  In that case, run executes in the invoking thread, and the code you thought would execute in the invoking thread never does, because run is running and doesn’t return immediately the way start would.

Another thing to keep in mind is that Applet.start and Thread.start are fundamentally different things.  Applet.start is designed to be overridden to do whatever needs doing when the user leaves a page.  Thread.start should never be overridden, because it contains the functionality that actually creates the new operating system thread.

Thread Control

We've seen that the Thread object and the underlying operating system thread are not the same thing. You can actually have a Thread object without an underlying operating system thread. There are two points in a Thread's life when this is the case: before the start method is invoked (the thread hasn't been created yet) and after the run method returns (the thread has already disappeared.).

Thus, the Thread class is actually made up of the underlying operating system thread (the run method) and a set of thread monitoring and control methods. Some methods can be invoked from outside the thread (outside the run method) and some can't. Table 6.2 shows which Thread methods can be invoked on the current thread, and which need to be invoked against an external thread.

Table 6.2: Which Thread methods can be invoked on the current thread.

Method Name   Applies to current thread            Applies to other thread  Applies to all threads

activeCount                               X

checkAccess    X          X

countStackFrames        X                     

currentThread    X

destroy X          X                     

dumpStack       X                     

enumerate                                 X

getName           X          X         

getPriority         X          X

getThreadGroup            X          X

interrupt                        X

interrupted        X         

isAlive  X          X         

isDaemon         X          X         

isInterrupted                  X

join                   X         

resume             X         

run        -           -           -

setDaemon       X                     

setName           X                     

setPriority         X          X         

sleep    X                     

start                  X         

stop                 X          X         

suspend           X          X         

toString            X          X

yield     X                     

Start and stop are used to create and destroy the underlying operating system thread. You must invoke start to create the thread, but you don't need to use stop to get rid of it. Most Applets rely on the return from the run method to destroy the thread.

We can also pause a thread with the suspend method. This halts thread execution but leaves the thread in memory, allowing us to resume execution right where we left off when suspend was invoked. Later on, with animation, we create a series of threads, and suspend or resume them according to the activity of the remote agents.

Join is another important thread control mechanism that we'll use later on in our applet initialization methods. The point of calling join is to simply wait for the thread you're calling join against to die. For Unix types, it's the equivalent of waiting for a child process. Join is actually an easy concept, made difficult by a stupid label. In real life, if I join you, we both continue to exist. In threads, when one thread joins another, the one joined must die before the joiner can continue. By all rights, this method should have been named “waitForTheDeathOf.”

Thread Attributes

Each thread has three attributes that can be set and queried: name, priority, and daemon status. The thread name is just that, an identifier we can attach to the Thread. We set the thread names within the Agent system, but never use them. I did this mostly for debugging/system monitoring purposes. Java itself doesn't use thread names for anything and there's no requirement for thread names to be unique. Internally to Java, threads are identified by the unique handle the operating system gives them.

Thread priority is also a straightforward concept, exactly analogous to process priority under Unix. Higher priority threads are scheduled (by the operating system) to run more frequently than lower priority threads. Priority can be changed on the fly via Thread.setPriority(). Different operating systems use different ranges for priority values. Some even flip the meaning, with lower values getting higher priority. Thus, Java defines three constants, MIN_PRIORITY, NORM_PRIORITY, and MAX_PRIORITY. Any time you set the priority of a Java Thread, use a number based on these constants.

The concept of daemon status, though common to most multi-threaded systems, is a little more difficult to explain. Normal threads are kept on a tight leash by the operating system. They can be joined by invoking Thread.join() which causes the caller to block until the thread dies. The Java interpreter also will not exit until all normal threads have terminated. In essence, the interpreter joins all non-daemon threads before exiting. Giving a thread daemon status essentially frees it from those controls, eliminating the possibility of joining it, and allowing the interpreter to exit without waiting for it to terminate. A thread should be made daemon unless:

*   Another thread needs to 'join' it

*   It absolutely MUST do some cleanup (other than memory management), like closing open files, before the system exits.

Daemon status is set via Thread.setDaemon(), which must be invoked before the Thread's start method gets invoked (i.e., before the underlying system thread actually starts executing). I generally put it in the constructor.

When Do Applet Threads Run?

One of the first questions that arises when you start spawning multiple threads is how do I know when each of these threads will be running? Consider the following scenario: a user enters a page which contains an applet and a single link to another HTML page. The browser reads the APP tag, loads the applet and sets it running. Now the applet is running. The user then clicks on the hyperlink, taking him to another page. The page with your applet in it disappears, but what happens to the thread running on the users machine that contains your applet code? The short answer, as you might expect, is that it stops running. No surprise there.

Especially for our application though, this presents real problems. For one thing, we want to view partial results--that is, we want to follow a hyperlink to a results page while our Agents are still in the field collecting more. The problem there is that if the AgentLauncher stops running the Agents in the field will stop dispatching and eventually die off.

What saves us in this situation is that any threads created by our AgentLauncher can be left running while the user hyper-links elsewhere.  All we have to do is break one of the cardinal rules of applet writing, which says that when the browser invokes Applet.stop against your applet, you should stop running.  When the browser invokes Applet.stop against our AgentLauncher, the only thread we terminate is the one that updates the display window, which of course we don’t need when the user is looking at another page.  All the other threads remain running.

Shared Resources and Synchronization

One issue that single-threaded programmers never have to deal with is coordinating access to shared resources. By shared resources, we mean anything that more than one thread needs to use. In most cases, this means a file, or a block of memory. Access usually needs to be coordinated because we're doing a series of operations on the resource that need to happen in one lump so that other threads don't see partial changes.

One of the ways we coordinate access to shared resources is via synchronized methods/blocks. There are many places in our application where synchronized execution might be useful, but the easiest to understand is probably the reportResults method in the AgentServer. When an Agent finishes his work on an Agent Server, he connects to the dispatching AgentServer and sends message containing a single URL pointing to his results HTML document. The dispatching AgentServer combines all these URLs into a single results page the user can jump to via the ResultsButton.

Each SocketHandler on the AgentServer runs in a separate thread. When the SocketHandler reads a results message over that connection, it calls AgentServer.ReportResults() to add that URL to the list of results URLs. You might think that funnelling result reporting through a single function guarantees thread-safety but that's not the case, because all the calls to reportResults are happening in different threads.

Consider what happens when two Agents, 007 and 99, report a result at the same time. 007 reports his results in http://www.info.com/result.html while Agent 99 reports her results in http://www.kaos.com/max.html. What we're hoping to see in the dispatching AgentServer results HTML document are two lines like those shown in the code snippet below:

Results from server <A HREF="http://www.info.com/result.html">www.info.com</A>.

     Time elapsed 3:21 qty 13k - price $2.30

Results from server <A REF="http://www.kaos.com/max.html">www.kaos.com</A>

     Time elapsed 1:15 qty 39k - price FREE

 

What would happen, though, if the two calls to AgentServer.reportResults() run at exactly the same time? You could end up with HTML like that shown in the code snippet below:

Results from server <A HREF="http://www.info.com/result.html">www.info.com</A>

Results from server <A HREF="http://www.kaos.com/max.html">www.kaos.com</A>

     Time elapsed 1:15 qty 39k - price FREE

     Time elapsed 2:30 qty 13k - price $2.30

 

That, of course, would be incorrect. One way we could account for this would be to synchronize access to AgentServer.reportResults(), as shown in Listing 6.4 with a synchronized reportResults method.

Listing 6.4: A synchronized method.

/** Add this Agents results to the cumulative results page that

we've been amassing for the whole run of this Agent.

@param  r The ResultMessage that the Agent is sending back to

the AgentLauncher.

*/

public synchronized void reportResults( ResultMessage r ) {

  // If this search has already been cancelled, don't bother

  if( resultFile == null )

    return;

  if( r.theURL == null ) {

    try {

      FileOutputStream fos =

        new FileOutputStream( resultFile );

      PrintStream ps = new PrintStream( fos );

      ps.println( "<P>" );

      ps.println( "Results from server <A HREF=\""+r.theURL+

          "\">"+r.server+"</A>");

      ps.println( "\tprice "+r.price);

      if( r.comment != null )

        ps.println( "\t"+r.comment );

      }

    catch( IOException ioe ) {

      System.out.println( "reportResults ioexc "+ioe );

      }

    }

  }

 

 

The purpose of this method is to print a String to the dispatching AgentServer’s results file.  The content of the String depends on the contents of the ResultMessage that is supplied as an argument.  The ResultMessage simply tells us where the Agent stored his results, how much they’ll to view. See Chapter 7 for the details of ResultMessage.  If theURL is null, then there were no results and we display that (via showNoResults).

The key feature of this method is the synchronized keyword in the declaration.  The situation we want to avoid is one where two threads invoke the method at the same time and our calls to println get interlaced as we talked about earlier.  The synchronized keyword in the declaration guarantees that only one copy of the method can be running at one time. Another way to put this is that our synchronized AgentLauncher.reportResults() can now only run in one thread at a time.

Efficient Serialization

Our synchronized reportResults method does what we need, but it actually does more locking than we really need. Synchronizing the method locks the entire method when all we really need to do is lock the block of code containing the two invocations of println. So we rewrite reportResults to use a synchronized block around those two lines, as shown in Listing 6.5.

Listing 6.5: Using a synchronized block.

/** Add this Agents results to the cumulative results page that

we've been amassing for the whole run of this Agent.

@param  r The ResultMessage that the Agent is sending back to

the AgentLauncher.

*/

public void reportResults( ResultMessage r ) {

  // If this search has already been cancelled, don't bother

  if( resultFile == null )

    return;

  if( r.theURL == null ) {

    try {

      FileOutputStream fos =

        new FileOutputStream( resultFile );

      PrintStream ps = new PrintStream( fos );

            synchronized( ps ) {

           ps.println( "<P>" );

        ps.println( "Results from server <A HREF=\""+r.theURL+

                "\">"+r.server+"</A>");

              ps.println( "\tprice "+r.price);

              if( r.comment != null )

                ps.println( "\t"+r.comment );

              }

      }

    catch( IOException ioe ) {

      System.out.println( "reportResults ioexc "+ioe );

      }

    }

  }

 

The method itself is no longer synchronized.  Multiple threads can invoke the method without blocking waiting for another thread to finish with it.  What we’ve added is a synchronized block within the method, so thatwe have the smallest possible window of time where one thread might be waiting for another to finish working.

Each object in the system has a lock associated with it. In most cases, the lock is not used. Various threads can use the object without restriction. However, when thread A invokes a synchronized method (or block), it 'acquires' the lock for the object associated with that synchronized method (or block). If thread B has already acquired the lock, thread A waits until thread B releases the lock before it executes the synchronized method. In our final reportResults method, we use the lock on our PrintStream, ps,  to synchronize our block. This locking mechanism is exactly analogous to the more familiar record, table and file locking that current operating systems and DBMSs provide.

As in DBMS record and table locking, we try never to lock more code than is absolutely neccessary. Each time a thread fails to acquire a lock, it (and possibly the user) waits until the lock is released. This is wasted time that should be kept to an absolute minimum.

Our first, unsynchronized, reportResults had the potential to misorder lines, but not to intermix the characters of one line with another. This is because println itself is synchronized. The moral: considerable care has been taken with the multithreading issues in Java, so it's often the case that the level of synchronization you need is already implemented. Don't go around synchronizing blocks and methods until you're sure the synchronization you need isn't already there.

It's often hard for people to grasp the pervasiveness of synchronization situations, especially if they're new to the multithreading world. For a quick indication, flip through the class documentation and see just how many of the methods are synchronized.

Subterranean Threads

You now have the wherewithal to go out and create billions and billions of threads of your very own, but even if your applet never explicitly creates a single thread of its own, there are already multiple threads in any Java applet. These are the four subterranean threads that always exist in a running Java program: the main interpreter thread, the finalizer thread, the idle thread and the garbage collector. The interpreter thread is the easiest to understand, since it's sole purpose is to execute the compiled Java byte-codes from the .class file. You can think of a traditional, single-threaded interpreter as one in which all the functionality is stuffed into the interpreter thread.

The idle thread, the garbage collector and the finalizer work together. The idle thread, running at a very low priority, simply maintains a flag that tells whether or not it has run. The idle thread's priority is chosen so that it almost never runs unless all other threads in the Java runtime are blocked. The garbage collector, running at an even lower priority than the idle thread, checks whether the idle thread has run. If the idle thread has been running, that probably means that it's a good time to run a garbage collection. The finalizer thread is a low priority thread that helps with garbage collection. After the garbage collector identifies an object as inaccessible, the finalizer calls the object's finalize method, if it exists, to do any cleanup that might be neccessary. The system tries to run garbage collection and finalization only when there is cpu time to spare. This design provides good performance for event-driven applications where the majority of real-time is spent waiting for events.

Other Threads

The subterrannean threads exist in any Java program - applet or application.  Applets have another whole class of peer threads - the browser threads.  Web browsers are all multi-threaded by design.  When you see a browser changing the status line, updating the screen and responding to user input all while downloading a graphics file, that’s a result of conscious multi-threaded design.  In most browsers, applets will not have access to any threads other than those the applet itself creates, but you should be aware that, in any browser, threads are coming and going all the time.

Inter-Process Communication

Java does not really support the notion of interprocess communication, not much of a surprise since it doesn't really believe in processes either. This has important implications for coders who've written multi-threaded or multi-process applications in C. As language features, DDE, semaphores, mailboxes, queues, OLE, system-wide shared memory and most other traditional forms of IPC are absent.

There is only one form of interprocess communication provided for Java applets--anonymous pipes via the Process and Runtime classes.  Table 6.? shows the methods provided with the Process class, while Table 6.? shows the Runtime class.

Table 6.?: The Process class.

Type     Method name    Description      

            Process            Constructor, takes no arguments.  Never called directly.

            destroy Kills the process.

int         exitValue           Gets the exit value for this process.

InputStream      getErrorStream  Returns a stream corresponding to standard error, stderr, for the process.  Allows Java apps to read the error output from the external process.

InputStream      getInputStream  Returns a stream corresponding to standard output, stdout, of the process.  Allows Java apps to read data from an external process.

OutputStream    getOutputStream           Returns a stream corresponding to standard input, stdin, of the process.  Allows Java apps to send data to an external process.

int         waitFor Blocks until the Process exits, returning the process' exit code.

 

Table 6.?: The Runtime class.

Runtime            getRuntime()     Returns the current Runtime.  Static method that must be called to get a Runtime to invoke any of the other methods against.

void      exit(int exitcode)            This method kills the interpreter.

Process            exec(String cmd)           Executes the command cmd, and returns a Process from which you can get stdin, stdout and stderr.

Process            exec(String cmd,String[] envp)   Executes cmd, passing it the environment specified by the String array envp.

Process            exec(String cmdplus[])   Executes the command specified by the first member of cmdplus, then passes the rest of the member of cmdplus as arguments to the command.

Process            exec(String cmdplus[], String envp[]       Executes the command specified by the first member of the cmdplus array, passing the rest of the members as arguments, and passing envp as the environment.

long      freeMemory      Gets the number of free bytes.  May or may not be accurate.

long      totalMemory      Gets the total size of memory.

void      gc        Run the garbage collector.  Should result in more free memory.

void      runFinalization   Runs finalization for any objects awaiting finalization.  Shouldn't need to be called directly.

void      traceInstructions(boolean On)     If On is true, all instructions are reported to standard out.

void      traceMethodCalls(boolean On)   Of On is true, all method calls are reported to standard out.

void      load(String filename)     Loads the dynamic link library specified by filename.

void      loadLibrary(String libraryname)   Loads the dynamic link library specified by libraryname.

InputStream      getLocalizedInputStream(InputStream in)            Creates a UNICODE stream from the local format stream in.

OutputStream    getLocalizedOutputStream(InputStream in)          Creates a local format stream from the UNICODE OutputStream out.

All the forms of Runtime.exec execute an external application that must already be resident on the local machine and connect its standard input, standard output, and standard error to new Streams that Java creates for your applet to read and write. This external application is not just another thread but a completely separate process. The only way to communicate with it is through the anonymous pipes provided by Process.getInput/Output/ErrorStream. Our AgentServer Java application uses exec to implement a layer of security on top of that already provided by Java. We could have easily written this method entirely in Java but instead we'll use the Process.exec to show how information providers might control access without having to trust Java or the agent itself.

Our new security feature specifies that each Agent must present identification and obtain a 'pass' from the server in order to run. Our Agent already carries identification with it that specifies the agent name, the particular run, and the person who dispatched it. So we'll implement a method in the AgentServer called obtainClearanceToRun. As a parameter, it takes our Agent ID and signature. Via the execin method, the AgentServer starts up a program called checkin on the server.  The byte array returned by our read of the InputStream is our 'pass' and must be presented to the AgentServer in order for us to get an OutputStream to write our results to. clearance.c is a small C program for Windows NT that implements a very rudimentary version of the clearance functions. Notice that the AgentServer must explicitly close the OutputStream. The point here is that, by getting outside Java, an implementor is free to write as elaborate a security system as he desires.

[KEITH: ANOTHER NO-BRAINER LISTING]

[INSERT obtainClearanceToRun source and clearance.c]

Listing 6.8: ?????

John: Let’s fill in that listing and give a little explanation. Thanks. --Scott.

Inter-Thread Communication

Java provides anonymous pipes for inter-thread communication via the PipedInputStream and PipedOutputStream classes. These are classic anonymous pipes that function just like the ones we're all familiar with from UNIX. The basic theory of PipedStreams is that you create one end, then create the other end passing it the handle to the first end in the constructor.

In the AgentLauncher applet, we use Piped Streams to build a local test bed for our AgentDispatcher. Since, unlike Sockets, PipedStreams are unidirectional we create a new class, TwoWayPipe, that creates two PipedStreams. read returns a read on the PipedInputStream, write returns a write on the PipedOutputStream and connect connects both PipedStreams to their partners in the TwoWayPipe that was passed as a parameter.

Listing 6.9: TwoWayPipe class.

[KEITH: FOLLOWING THREE LISTINGS ARE NO-BRAINERS, BUT I'LL HAVE TO GO BACK TO THEM.]

[INSERT TwoWayPipe class]

John: Please fill in listing & explanation.  --Scott.

 

That takes care of the AgentLauncher side of the test bed, but we need something for the AgentConnectionHandler's TwoWayPipe to talk to, something that runs independently and mimics the activity of a remote agent. So we create a new, runnable class--LocalAgent.

Listing 6.10: The LocalAgent class.

[INSERT LocalAgent class]

John: Please fill in listing & explanation.  --Scott.

To finish the test bed, we add a button in the AgentLauncher to start the sequence. The StartTest method sets the AgentLauncher up in LAUNCHED mode, creates ten AgentConnectionHandlers and ten LocalAgents to talk to them, then sets the LocalAgents going. With the randomness built into the LocalAgents, the test usually runs two or three minutes. We could get a lot more involved and add a dialog that would allow us to vary the number and character of the LocalAgents, but for now, this will do. Figure 6.B shows … ???

[INSERT BUTTON FROM AGENTLAUNCHER FOR LOCAL TEST]

Figure 6.?: A new button allows us to launch a test agent locally.

John: This looks like an actual figure. Please fill in the blanks, including the callout in the above paragraph. --Scott.

This little test sequence tests most of the non-Socket specific AgentConnectionHandler code and a lot of the AgentLauncher code. What it doesn't test is the connection sequence, which is very Socket specific. Since there's no PipedStream analog to the Socket.listen() method, we're reduced to creating an array of AgentConnectionHandlers and explicitly connecting a LocalAgent to each of them.

Inter-Applet Communication

Inter-applet communication is not supported as such, but, using the AppletContext interface we talked about in Chapter 4, it is relatively easy to get applets to communicate.  In fact, once an applet retrieves an Applet object from the AppletContext, communication is as easy as invoking a method, or setting a public variable.  There is no need to use extraordinary mechanisms like sockets, or anonymous pipes.

Listing 4.8 shows an example of a relatively unfriendly applet-to-applet conversation.

Thread Groups

In Chapter 5 we saw how Java groups Components into Containers to facilitate operations on collections of Components.  The process control analog to Container is ThreadGroup.  ThreadGroup exists for two purposes - to group threads into one lump where they can be operated on as a group, and to protect threads from each other.

ThreadGroups methods can cut into three easily understood sets:

Methods that operate on the group itself - name priority ...

activeCount

activeGroupCount

checkAccess

destroy

getMaxPriority

getName

getParent

isDaemon

parentOf

setMaxPriority

toString

uncaughtException

Methods that operate on each member of the group - stop, resume ...

list

resume

stop

suspend

Enumerations

enumerate

enumerate

enumerate

enumerate

 

When to Thread

We've seen how useful and neccessary threads are, so by now you should be bursting to turn every Integer into its own thread. Hold on and take a deep breath. Threads ARE useful and neccessary, but they have limitations that you need to consider too.

For one thing, context switching involves some amount of overhead, so every thread you create adds some drag to the overall system. It only makes sense--the operating system spends cycles switching between threads and the more threads, the more cycles the OS spends context switching.

Threads are also a system resource and most operating systems have a hard limit on both the total number of threads in the system and the number of threads that one user can create. Think of it this way: if you spend 500 threads making “active” integers for your rotisserie baseball league scoring application, then your DBMS may run out of transaction processing threads--a bad tradeoff. For total threads, a number somewhere around one thousand is typical. Per-user threads will be something less than that.

When deciding whether or not to thread an object, I tend to concentrate first on the difference between active and passive objects. A network socket is an active object, an integer is not, keyboard--active, file--not. Most objects that respond to events generated outside the application deserve their own threads.

A final reason to get with the program when it comes to threads: Java provides weak to non-existent support for polling IO. There is no analog to the Unix select system call which so many Unix programmers rely on for polling IO. If you intend to do network IO you will probably want to write it as we have here, as blocking IO in a separate thread.

Animation

So far, we've threaded our Applet and AgentDispatcher communication classes. That's all well and good, but efficient multi-tasking and well structured communication code is not what brought us here, is it? Admit it, you really bought this book just to put cartoons on your home page, didn't you.

Well having gone through the applet, awt and lang packages, we now have all the tools to do that. What we'll do now, is modify the AgentFace class so that while the Agent is off doing his work on the remote AgentServer, the corresponding graphic representation on the users screen will be animated. From a UI standpoint, this is a major advance, as the activity on the screen better represents the activity happening behind the scenes out on the net.

Wait and Notify

We implement our animation the same way the old cartoonists used to - by flashing a new image on the screen at regular intervals to give the illusion of motion.  To do that, we need to load a potentially large series of images into memory over the network.  This image loading is not just a lengthy operation, but it is also unpredictably lengthy.  For this reason, browsers retrieve images in a separate thread.  We cannot tell from a call to Applet.getImage whether or not the image we’re trying to draw has been loaded over the net yet.  In fact, if we simply call getImage for all our images, then try to draw them with Component.drawImage many of them will not actually get drawn for a long time.  The only indication we ever get that our images have NOT been loaded is a bad return value from drawImage.

How do we deal with this?  We could simply sit in a loop calling drawImage until the bad return value changes, but that wastes a lot of CPU cycles.  There’s a better way.  Using the ImageObserver interface and the wait-notify thread control mechanism we can have our main Applet thread block (sleep) until all the images are loaded.  Listing 6.?? shows how the AgentLauncher implements an ImageObserver/wait-notify solution to image loading.

Listing 6.??: Image loading in the AgentLauncher.

The first thing to look at is the inheritance chain for Applet back in Figure 5.1.  Back among its superclasses is Component.  If we look at the declaration of Component we see that it implements the ImageObserver interface shown in Table 6.??.  Like many interfaces, ImageObserver requires a single method - imageUpdate.  In Component, imageUpdate simply repaints the Component whenever it gets called.  We will override it and use it for a little different purpose.

The next stop on our image loading journey is our own ImageLoader class.  The purpose of ImageLoader is to consolidate the loading of an array of images into a single class.  As such, we pass the constructor an AgentLauncher to use for updating the status line, an array of image file names, and a graphics context that it can draw those images into.  The graphics context is an off-screen context which we’ll explain in a minute.

The ImageLoader constructor copies the image file name array into a local array and makes a URL out of it.  If the URL is okay, then it calls back to Applet.getImage to retrieve a hollow Image object.  getImage does NOT load the image over the net.  The next call, g.drawImage is the key to this operation.  g is our off-screen graphics context and calling drawImage against g causes the browser to actually go and fetch the image over the net in a separate, asynchronous, image loading thread.  drawImage returns immediately, usually with a negative return value.  We’ve told the graphics context to draw our image but it has essentially said, “okay, I’ll get around to it”.  We use an off-screen graphics context because we don’t really want these images to appear on the screen - we just want to load them over the net. 

The last argument to drawImage, an ImageObserver, is the hook that allows us to know for certain when our images have actually been loaded.  We’ve passed our AgentLauncher (which implements ImageObserver via its superclass Component) as this argument.  What will happen now that we’ve called drawImage, and passed it an ImageObserver, is that the asynchronous thread that loads images will call ImageObserver.imageUpdate whenever there is some progress in loading the image. 

So now we have a mechanism in place where the image is loading over the network, and the image loading thread is calling one of our methods at regular intervals to tell us how it’s going.  All we need to do now is force our main applet thread to block until the image loading thread has told us that all the images are loaded. 

For this, we look to two places - our own LoadAnimationImages method, and our implementation of the ImageObserver.imageUpdate method.  In LoadAnimationImages, we create one of our ImageLoader objects for each animation (array of images).  When the ImageLoader constructor returns, the asynchronous image loading thread has started loading the images for that animation over the net.  So by the time we reach the while( notloaded ) loop in LoadAnimationImages, the asynchronous image loading thread is busy downloading bytes over the net.

The while loop asks each ImageLoader if all of its images have been loaded yet.  If all the images for all the ImageLoaders have been loaded, the loop terminates.  If they haven’t, we call Object.wait.  What wait does is just that - wait.  The thread in which wait was called blocks until it is notified by the aptly named method, Object.notify.  So we reach the bottom of the while loop, and our main applet thread blocks in the call to wait. 

Think about what is going on within the browser right now.  The main applet thread is blocked, but the asynchronous image loading thread is still running.  Not only is it running, but it is continually calling our imageUpdate method with progress reports on how the image loading is proceeding.  The next step is to see what happens within imageUpdate.

The first argument to imageUpdate is the hollow Image object that the image loading thread is trying to fill in.  This tells us which image we’re talking about.  The second argument, infoflags is the key.  This tells us what state the loading of this image has reached.  If we go back to the definition of the ImageObserver interface in Table 6.??, we see a number of integer constants defined which represent the various stages of image construction.  There are only two states that interest us - ERROR and ALLBITS.  If there is an image loading error, infoflags==ERROR, we abort the whole process by setting bImageLoadingError.  This is a bit abrupt, but suffices for now.  If the image has been completely loaded, infoflags == ALLBITS, we tell the ImageLoaders to mark this image as loaded.  Then we close the circle by calling Object.notify.

Remember what’s happening.  The main applet thread is blocked in the call to wait, but the image loading thread has invoked imageUpdate.  When imageUpdate calls notify, the main applet thread wakes up and the call to wait returns.   This is how wait and notify work.  wait blocks, and notify unblocks it.

How does imageUpdate’s while loop terminate?  The main applet thread is blocked in a call to wait.  When the last bit of the last image has been loaded, the image loading thread makes one last call to imageUpdate with infoflags set to ALLBITS.  imageUpdate tells the ImageLoaders that this last image is done which means that ImageLoader.allLoaded will now return true.  Then imageUpdate unblocks the main applet thread by calling notify.  Our call to wait returns, we run through all the ImageLoaders asking them if all their images have been loaded via allLoaded.  They return true, and the loop exits.  At this point all the bits for all the images are now in memory on the user’s computer and we can call Component.drawImage with complete confidence that the image will actually appear on the screen.

In any animation sequence you get two choices, use predefined images, or draw the pictures yourself. For our first shot, we'll use a series of GIF files.

In Chapter 5, we loaded a series of images and displayed them in an auto-flow container. The flow container arranged the images in symmetric rows. In order to animate these images, we have to find out where the flow window put them, so we create a findImage method.

[THIS ONE NEEDS WORK]

Listing 6.11: The findImage method.

John: Please insert listing and explanation.  --Scott.

 

Due to the possibly immense number of images, we don't want to create a new thread to animate each image. Instead, we crank up the rate of the main thread's run method and walk through the animations at random rates.

Listing 6.12: The Animation class and its AgentLauncher support.

/** Class representing an animation. Each animation is a series of

frames of consistent and constant size that get displayed at the same x

and y coordinates for a fixed amount of time.  There are two types of

animations, one-shot and looping.  One-shot animations run once, looping

animations run continually until they are stopped from outside.

 

@author John Rodley

@version 1.0 October 1, 1995

*/

 

public class Animation extends Object {

/** This is the x coordinate of the left edge of the picture. */

public int x;

/** The y coordinate of the bottom of the picture. */

public int y;

/** The size of the picture. */

public int size;

/** Is this animation actually animating now? */

public boolean bRunning;

/** The images comprising the stages of the animation. */

public Image images[];

 

/** If you want the image to disappear when the animation stops running,

set this to true.  Otherwise the animation will FREEZE when it stops

running. */

public boolean bDisappear;

 

/** If this animation runs only one, this gets set to true.  If this

is a loop that restarts at the beginning, set this to false. */

public boolean bOneShot;

 

/** This index indicates which frame of the animation will be displayed

next. */

public int FrameNumber;

 

/** Create an animation that is not animating right now. */

public Animation(boolean bOnceOnly, boolean bClear, int fixedX, int

fixedY, Image im[] ) {

     bRunning = false;

     FrameNumber = 0;

     bOneShot = bOnceOnly;

     bDisappear = bClear;

     x = fixedX;

     y = fixedY;

     images = im;

     }

 

/** Set this animation to active.  The first frame will be seen on the next

call to the paint routine. */

public void start() {

     bRunning = true;

     FrameNumber = 0;

     }

 

/** Stop this animation.  */

public void stop() {

     FrameNumber = images.length;

     }

 

/** Paint a single frame of this animation within an existing Graphics

context. */

public void paint(Graphics g) {

     if( FrameNumber == images.length )

           {

           if( bOneShot == true )

                {

                if( bDisappear == true )

                     {

                     g.clearRect( x, y, images[0].width,

                           images[0].height );

                     }

                bRunning = false;

                return;

                }

           else

                FrameNumber = 0;

           }

     g.drawImage( images[FrameNumber], x, y );/** The images comprising the stages of the animation. */

     FrameNumber++;

     }

}

 

/** From the AgentLauncher ... */

Vector animation;

 

/** Update the window, erasing things from their old positions and

painting them anew at their current positions. */

public void update(Graphics g) {

     int i;

     for( i = 0; i < animation.elementCount; i++ )

           {

           Animation a = animation.elementAt( i );

           a.paint(g);

           }

    }

John: A little code explanation?  --Scott.

Using Non-Daemon Threads

Up to now, all our threads have been truly asynchronous, daemon threads. The state of any of these threads didn't matter to any of the other threads. Our ImageLoader is a different story. If we try to use any of the Images in the AgentImages array before the ImageLoader loads them, we'll throw a nullPointer exception and, most likely, blow out of whatever method we're in. This is where we need to use non-daemon threads and the Thread.join() method.

In our ImageLoader.start() we set daemon status to false. Then we find the first use of any of the AgentImage arrays. That occurs down in the paint method, which traces back (via the Window object) to the call to repaint in our run method, so we drop three calls to Thread.join right before the while-loop in the run method. When the main thread calls ImageLoaderThread[i].join, Java blocks the main thread until the system thread associated with ImageLoaderThread[i] dies. If you want to see join at work, drop printlns around the join calls in AgentLauncher, and around the ImageLoader run loops. You'll see that almost all the ImageLoader work happens while the AgentLauncher is blocked in the join calls. This is shown in Listing 6.14.

Listing 6.14: Ensuring that images are loaded.

/** The main loop for the main thread. */

public void run() {

     // The AgentImages are used in the paint methods, so we have to

     // wait here to make sure all the images are loaded.

     for( int i = 0; i < 3; i++ )

           ImageLoaderThread[i].join();

     while( myThread != null )

           {

           Thread.sleep( 20 );

           repaint();

           }

     myThread = null;

     }

John: A little code explanation?  --Scott.

Native Methods

The first thing I always look at when reviewing a new language like Java is how to get out of it. This is only natural, as most programmers are already productive in one language and most projects need some of the functionality of last year's language in the new product they're writing in next year's language. Windows GUI development languages, like Easel or Toolbook, usually provide the ability to make DLL calls and Java provides an analogous escape hatch--native methods.

Native methods allow you to link dynamic libraries written in C, C++, or some other language via a method “wrapper.” Basically, if your DLL call obeys certain rules about parameter passing, memory management and such, you can write a method that simply calls out to that external DLL. Native methods are crucial for shrink-wrapped, stand-alone Java applications where your program is competing against code written to the native operating system API. Native methods, however, are not useful for the purposes of our application for two reasons. First and most important, by definition a native method is not portable and one of the base requirements for our application is that it run in any Java-capable browser. Second, and perhaps not so obvious, is the whole question of how you would get your native method onto a user's machine. The Java classloader is designed for moving java .class files, not Windows .dll files. Thus, you could write the world's coolest native method, but any Java-capable browser trying to run your applet over the net would throw an exception when it tried to (locally) load your native method.

Conclusion

Java makes threading simple and worthwhile. You can write Java applets the same way you used to write single-threaded applications in C or C++, but you'd be wasting much of the power of the language. The current generation of browsers are all multi-threaded, and threading will only become more prevalent as a method of increasing the real and apparent efficiency of applications.


Exercises

1. If we execute the following method:

void MakeAThread() {

     MyThread t = new MyThread();

     }

where MyThread is declared as “extends Thread”, does this code actually create a new operating system thread?  How would you write the MyThread constructor so that it does, or doesn’t?

2. What is the difference between Thread.pause and Thread.stop?

3. Why would browser SecurityManagers want to prevent applets from executing external programs on a local workstation?

4. If the following method starts executing in operating system thread A, and the newly created thread is operating system thread B, which operating system thread does each numbered line of code execute in?

void MakeAThread() {

1.  MyThread m = new MyThread();

2.  m.start();

3.  try { Thread.sleep( 1000 ); }

4.  catch( InterruptedException ie ) {}

5.  m.changeVar( 100 );     

6.  m.stop();

     }

public class MyThread extends Thread {

6.  int variable = 0;

     public void run() {

7.        while( true ) {

8.             try { Thread.sleep( 1000 ); }

                catch( InterruptedException ie )

9.                  {System.out.println( “interrupted”);}

10.                System.out.println( “variable = “+variable);

                }

           }

     public void changeVar( int j ) {

11.           variable = j;

           }

     }

5. What is the difference between daemon and non-daemon threads?