|
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.
| |