Pretend like you'd like to write a Java program that wants to launch and lightly manage
some operating system processes. Know how to do this? You'll want to use one of the
flavors of the
Runtime exec() methods.
If you happen to be running on a J2SE 1.5 or greater JVM, you can use the new
ProcessBuilder
class, but there's seems to be not much benefit to doing so, and of course, your code won't
run on a J2SE 1.4 or earlier JVM if you do. Both Runtime.exec() and ProcessBuilder
end up returning an instance of the Process class, which models the launched operating
processes (the child).
One of the things I learned the hard way with the Process class, many years ago, was that you really need
to process the stdout and stderr output streams of the child process, because if you
don't, and your child process writes more than a certain amount
(operating system dependent) of data on those streams, it'll block on the write, and thus
'freeze'. So, you need to get the stdout and stderr streams via the
(confusingly named) getInputStream() and (logically named) getErrorStream()
methods of the
Process class.
The simplest thing to do to handle this is to launch two threads, each reading from
these input streams, to keep the pipes from getting clogged. Do whatever you need to do
with the data being output by your child process; I'm sure you want to do something
interesting with it.
I've not really had a chance to work with the
java.nio
package before, so I happened to think that this would be a good chance to play; let's see if we
can get from two threads handling the child process's output, down to one,
by using the class
Selector,
which would seem to be the moral equivalent of the *nix function
select(),
which can be used to determine the readiness of i/o operations of multiple handles at
the same time.
Looking at Selector, you can tell right from the top of the doc, that this class deals with
SelectableChannel objects. Now, you need to figure out how to get from an
InputStream (returned by Process.get[Input|Error]Stream()) to
a SelectableChannel. Except, you can't. From any InputStream,
you can call Channels.newChannel() to get a ReadableByteChannel,
but a ReadableByteChannel is not a SelectableChannel.
(ReadableByteChannel is an interface, and SelectableChannel
is a class.)
This is not the end of the world; it may
just be that the object returned by Channels.newChannel() is actually
an instance of SelectableChannel (or a subclass thereof). Never know.
So, here's a little experiment for an Eclipse scrapbook page:
Process process = Runtime.getRuntime().exec(new String[] {"sleep", "10"});
java.io.InputStream iStream = process.getInputStream();
java.nio.channels.ReadableByteChannel channel = java.nio.channels.Channels.newChannel(iStream);
System.out.println(channel.getClass().getName());
System.out.println(channel instanceof java.nio.channels.SelectableChannel);
and the result is ...
java.nio.channels.Channels$ReadableByteChannelImpl
false
Bad news; it's not a SelectableChannel, and would appear to be an instance of an inner class
of the Channels class itself. Poking into inner class, that class is a subclass of
java.nio.channels.spi.AbstractInteruptibleChannel. Not selectable at all. All this
inner class business is for the default 1.5 JRE I use on my mac. Other implementations
might well be different (and better), but that doesn't help me on the mac.
So, unfortunately, you can't reduce my two threads to process a child process's stdout and
stderr down to one, using this select technique.
This might not sound so bad, but what if you wanted to be able to handle
a lot of processes? To handle N simultaneous processes, you'll need N * 2 threads to
process all the stdout and stderr streams. If you could have used the select logic, you'd only
need 1 thread.
Note that you could also try polling these streams, by calling
available() on them, to determine if they have anything to read. Polling isn't
very elegant, and is obviously going to be somewhat cpu intensive. But for me, I got
burned on available() a long time ago, don't trust it, and never use it.
Bummer.
But wait, it gets better!
Another thing you're going to want to do with these child processes is to determine
when they're done. There's two ways of doing this. You can call
Process.exitValue() which returns the exit value of the process. Unless the
process hasn't actually exited, in which case it throws an
IllegalThreadStateException. The other way to determine when the process
is done is to call Process.waitFor(); this method will block until the
process has exited.
Neither of these is very nice. If you use waitFor(), you'll burn
a thread while it blocks (now up to N * 3 threads per process!).
If you use exitValue(), you can poll, but
every check of the process, while it's not complete, is guaranteed to throw
an exception, which is going to burn even more cpu.
Double bummer.