Check out the new USENIX Web site.
Using JavaUSENIX

 

by Rik Farrow
rik@spirit.com

February, 1997

By now, there's a new release of the Java Development Kit (JDK) available, version 1.1. At the time this article was written, 1.1 was in Beta, and just released, so I haven't tested it yet. The Javasoft server has been upgraded, so getting a new version of the JDK is easier than before (try: https://www.javasoft.com/products/JDK/1.1/java/README).

Improvements include internationalization (which I wish I had known about in Norway), signed applets, a method for creating archives (jar), a completely rewritten AWT for Win32, networking, and other "enhancements." Included in this release is support for database connectivity and remote method invocation. True database connectivity is the holy grail of client/server, and I for one am very curious about how much Sun will be giving away. The remote method invocation raises shades of DCE (OSF's Distributed Computing Environment), not because I think Java will use it, but more because it will be different.

Also, I discovered that guavac is a Java cross compiler, not the complete environment, so I am still looking for a Sun implementation. Actually, I will probably either use the existing Linux version or one that works with BSD/OS.

Threads

In the last column, I provided a simple networking example. The server code contained a Thread, which waited for connections, then served the connection by returning the date. I mentioned then that a server that did more than quickly return an answer required a separate Thread for each connection. For an example of a server that creates a Thread for each connection, try: https://www.ora.com/catalog/javanut/examples/section7/Server.java.

I had considered providing an example showing how to write an application that takes URLs referring to a Java class, uploads the class, dynamically adds the class to the runtime, and executes a method in the class. This operation is similar to what Java-enabled browsers do when they load applets. But as I worked on my example, I realized that I should talk about Threads first.

Threads are execution contexts, similar to UNIX processes but with important differences. Threads are sometimes called lightweight processes because they don't have the "heavy" context that UNIX processes have. Creating a UNIX process entails allocation of memory and copying the existing process to this newly allocated memory (the fork() system call). vfork() speeds this process up by copying only those portions of the existing process as pages of it are accessed. The process memory space includes the execution environment and is protected by the system's memory management hardware from access by other processes.

In Java, Threads share the same process memory space. They also share most of the execution environment, so creating new Threads is faster than creating new processes. What you gain with Threads is the ability to have more than one path of execution in the same program. In a UNIX process, there is only a single path of execution. Each Thread also has a single path of execution. But with a single process, running a Java Virtual Machine, there will always be more than one Thread, each with its own path of execution.

Thread Control

A Java application or applet that requires additional Threads must either subclass the Thread class or implement Runnable. This new class must contain a method named run() that contains the code to be executed by the Thread. When the run() method returns, the Thread exits, sending a ThreadDeath exception to its parent Thread. The parent Thread can catch this exception, much like a UNIX process can have a signal handler catch a child death signal. In Java, if ThreadDeath is caught, it must be thrown again because the runtime uses ThreadDeath as a signal to clean up the Thread.

You don't directly call the run() method (unless you don't want it to run in its own Thread). Instead, Threads are started with the start() method, suspended with suspend(), resumed with resume(), and halted with stop() (which also causes ThreadDeath). A suspended Thread can be resumed at the instruction in the run() method where it was suspended.

The Java Virtual Machine (the runtime) includes a Thread scheduler. Each Thread has a priority, an integer value between one and ten, with higher values reflecting higher priority. The Thread scheduler should choose the active, ready-to-run Thread with the highest priority to run next.

The Thread scheduler is not as rigorously specified as the rest of Java, and there are definitely variations here. For example, the JDK 1.0.2 Solaris version is not pre-emptive, but the Win32 scheduler is. This is exactly the opposite of what I expected. A non-pre-emptive scheduler will not interrupt an executing Thread, much like the behavior of Windows 3.1. The Win32 Java Thread scheduler will interrupt a running Thread, resulting in fairer scheduling. For example, if you start up two Threads with long running loops under Solaris, the first Thread started will complete before the other Thread starts. But under Windows 95 or NT, the Threads take turns.

ThreadGroups

Just one more point, and I'll be ready for an example. Threads are always col lected into ThreadGroups. If a ThreadGroup is not explicitly included when the Thread is created, the Thread shares the ThreadGroup with its parent. Besides being a method for organizing Threads, ThreadGroups can also be used by the SecurityManager. The SecurityManager's checkAccess() method gets called whenever one Thread attempts to change another Thread, for example, to stop() a Thread or change its priority. SecurityManagers, such as the one used in the Netscape browser, limit access, for example, to other Threads within the same ThreadGroup.

Netscape has many Threads running anytime you have Java enabled. Also, if you have loaded other applets that have failed to destroy their Threads, these will also still be around, perhaps even running. You can take a look at the Threads and ThreadGroups running in your Java-enabled browser by trying an applet written by David Flanagan (try: https://www.ora.com/catalog/javanut/examples/section9/AppletThreadLister.html). This applet creates a TextArea object for displaying active Threads and ThreadGroups.

The application I created for this column enables you to play with the scheduler for the runtime you are using. The application creates two instances of Sibling, a subclass of Thread, at priorities that you set on the command line. Each Sibling counts down from one million, printing status reports every 100,000 iterations, permitting you to see how the scheduler works on your implementation. The application also displays all the active Threads in the runtime. Here's the output from an example run:

C:\java\rik> java Sibling 7 4 
java.lang.ThreadGroup[name=system,maxpri=10]
    Thread[Finalizer thread,1,system] 
    java.lang.ThreadGroup[name=main,maxpri=10]
        Thread[main,5,main]
        Thread[Older,7,main] 
        Thread[Younger,4,main]
Older: 900000 
Older: 800000 
Older: 700000 
Older: 600000 
Older: 500000 
Older: 400000 
Older: 300000 
Younger: 900000 
Older: 200000 
Older: 100000 
Older: 0 
Younger: 800000 
Younger: 700000 
Younger: 600000 
Younger: 500000 
Younger: 400000 
Younger: 300000 
Younger: 200000 
Younger: 100000 
Younger: 0 
Main Thread exiting. 
C:\java\rik>
In the example run, the first Thread (the Older one) almost completes before the Younger Thread gets a chance to run. This is what you might expect, because the Older Thread has a priority of seven, and the Younger only four.

You might also have noticed that there are two ThreadGroups shown in the listing, and three Threads (although we will create only two). The ThreadGroups are created by the runtime, as is the Thread used to invoke the main() method. Let's look at the example that created this output. (Click here for the complete example.)

public class Sibling extends Thread {

    // Constructor for Sibling 
Sibling(String name, int priority) { 
    super(name); 
    setPriority(priority); 
}

    // The run() method, with a long, tight loop 
public void run() { 
    int count = 1000000; 
    // sleep long enough for both Threads to be listed 
    try { 
        sleep(200); 
    } catch (InterruptedException e) {};
    // Countdown, with reports every 100,000 iterations 
    while (count-- > 0) { 	
        if (count%100000 == 0) 		
            System.out.println(getName() + ": "+ count); 
    } 
}
There is nothing very special so far. The constructor, the initializing method, simply calls super(name), so the superclass will create a Thread with the given name and sets the priority for this Thread. The run() method will sleep for 200 milliseconds, then loop one million times, displaying status every 100,000 loops.
private static void usage() {
    System.err.println("Usage: java Sibling p1 p2" + 
    	"where p1 and p2 are greater than 0\n and" + 
    	"less than or equal to 10"); 
    System.exit(1);
	}

private static int checkArg(String arg) { 
    int i = 0; 
    // Make certain we were given an integer 
    try {
    	i = Integer.parseInt(arg);
    } catch (NumberFormatException e) { usage();} 
    if ( i < 1 || i > 10 ) usage(); 
    return i; 
    }

 public static void main(String args[]) {
    if (args.length != 2) usage();
    int p1 = checkArg(args[0]);
    int p2 = checkArg(args[1]); 
    // Args are okay, let's get
    // grandparent ThreadGroup 
    ThreadGroup next = 
         Thread.currentThread().getThreadGroup();
    ThreadGroup top = next; 
    while (next != null) { 
    	top = next; 
    	next = next.getParent(); 
    } 
    // Now, create and start two Siblings 
    Sibling sib1 = new Sibling("Older", p1); 
    Sibling sib2 = new Sibling("Younger", p2); 
    sib1.start(); 
    sib2.start(); 
    // List all ThreadGroups and Threads 
    top.list(); 
    // Wait for lower priority Sibling to complete 
    Sibling lower = (p1>p2)?sib2:sib1; 
    try { lower.join(); } 
    catch (InterruptedException e) {};
    System.out.println("Main Thread exiting."); 
    } 
}
The usage() method has an obvious purpose - display the usage message and exit. The checkArgs() method attempts to convert the string argument to an integer value and checks that the value is between one and ten. The main() method is more interesting, especially after checking the arguments passed. Note that Java's first argument is not the name of the class. The C runtime stores the basename of the program executed in args[0], but Java puts the first argument there instead because you can get the classname with the getName() method of Class.

We next get the current Thread, find its ThreadGroup, then loop until we have reached the parent of all ThreadGroups. Once we have run the application, we can see that a loop is unnecessary and that the parent of the main ThreadGroup is system, the top-level ThreadGroup.

Next, we create the two Siblings, start() the Threads, and call the list() method on the top ThreadGroup. The list() method recursively calls the toString() method on each ThreadGroup and their component Threads.

Joining a Thread is a little like the UNIX system call wait() - one Thread waits until another completes. But in UNIX, you can't choose which child process to wait for. In the example, the program picks the Thread with the lower priority, which should be the last to complete.

There is a wait() method in Java, but it has a different purpose. Java includes an object locking mechanism, used to control access to objects when more than one Thread may access an object in a manner that would leave the object's data in an incorrect state. If one Thread has acquired the lock on a synchronized object, the other Threads that need access to that object can call wait(), which blocks until dthe lock on the object has been released and the Thread that had the lock calls notifyAll().

New Old Book

I have mentioned the O'Reilly Java in a Nutshell book twice in this column. It is my favorite book when it comes to a portable reference. I recently found the definitive reference for Java's core APIs: The Java Class Libraries (Chan and Lee, Addison-Wesley, 1996). This book is part of Sun's Java series and sat on my desk because it was too heavy to take with me (almost 6 pounds, 2.5 kilos, 1,658 pages). I had found that a lot of the material in the Java series could be found as online documentation, and expected this book to be the same. On the contrary, there is more information about the Classes and methods in the core APIs than I have seen anywhere.

I still don't plan on carrying the Chan-Lee book around, but I do plan on using it. There is certainly something very substantial about their book.

First published in ;login:, Volume 22 No. 1, February 1997.

 

?Need help? Use our Contacts page.
Last changed: May 16, 1997 pc
Java index
Publications index
USENIX home