| Books & Tools | Techniques | |
|
Comprehensive coverage of Ruby 1.8 and 1.9
"The New Most Important Ruby Book" JudeJude is my Java documentation browser. It combines Sun's definitive javadocs with the easy-to-use format of Java in a Nutshell, and tops it off with easy keyboard-based navigation and full-text searching. Jude is available for free evaluation. See the user's guide for more info Java in a NutshellThe 5th edition is now out, with complete coverage of Java 5.0! It includes a fast-paced tutorial on the language, and a compact quick-reference for the core Java API. Java Examples in a NutshellThe 3rd edition, updated for Java 1.4 This edition has all-new coverage of the NIO and JavaSound APIs, completely rewritten Servlets and XML chapters, and coverage of new Java 1.4 features (assertions, logging, preferences, SSL, etc.) added througout. A great book for those who like to learn by example. 193 working examples: 21,900 lines of carefully commented code to learn from. Java 1.5 Tiger: A Developer's NotebookAmazon incorrectly credits me as the main author on this book. I'm actually the second author: really more of a consultant. This is a good book about all the language changes in the latest version of Java. Effective JavaI didn't write this excellent book, but I wish I had. Author Josh Bloch is probably best known for the collections classes in the java.util package. His experience and wisdom are apparent in this book. I learned from it and recommend it highly. |
April 23, 2004Concurrent channel copying algorithmI've been studying the new java.util.concurrent package. And, as part of this, I've been reading Doug Lea's book Concurrent Programming in Java: Design Principles and Patterns. The book is well written and enlightening, and the package is powerful and has some very cool utilities in it. Inspired by an example Doug gives in both the book and the javadoc, I've written a concurrent version of the basic New I/O byte channel copying loop. This is the loop you write anytime you need to copy bytes from one channel to another. A proxy server would use this, for example, to copy bytes from one socket to another. Here it is in the traditional single-threaded form:
// This method is the cannonical New I/O channel copying loop
// implemented with a single buffer and a single thread.
static void copy(ReadableByteChannel source,
WritableByteChannel destination) throws IOException {
ByteBuffer buffer = ByteBuffer.allocateDirect(32 * 1024);
while(source.read(buffer) != -1) {
// Prepare the buffer to be drained
buffer.flip();
// Make sure that the buffer was fully drained
while (buffer.hasRemaining()) {
destination.write(buffer);
}
// Make the buffer empty, ready for filling
buffer.clear();
}
}
Now here's a concurrent version that does the same thing. (And presumably it does it faster: if there is enough I/O latency, the reader thread can run while the writer thread blocks and vice versa.) This version uses two threads and two byte buffers. The trick is that the threads use a java.util.concurrent.Exchanger to swap buffers. When the reader thread has filled up its buffer and the writer thread has drained its buffer, they swap them. When a thread exits because it has encountered EOF or an IOException, its swaps null to tell the other thread to exit, too. The basic buffer-swapping code is simple. The rest of the code is New I/O stuff, and exception handling.
// Here is a concurrent version of the same copying operation, using
// two threads and two buffers exchanged with an Exchanger. In theory,
// this should be more efficiently because IO latencies will allow the
// two threads to run concurrently
static void copyConcurrent(final ReadableByteChannel source,
final WritableByteChannel destination)
throws IOException
{
// The two threads use this to exchange full buffers for empty buffers.
// One thread tells the other to exit by exchanging null with it.
final Exchanger<ByteBuffer> exchanger = new Exchanger<ByteBuffer>();
// This is the definition of the reader thread class
class Reader extends Thread {
public void run() {
try {
// Start with an empty buffer
ByteBuffer buffer = ByteBuffer.allocateDirect(32 * 1024);
for(;;) {
// Invariant: buffer is empty and ready to fill here.
// Try to read some bytes. Exit the loop if the
// channel has no more bytes or on IOException
try {
int numread = source.read(buffer);
if (numread == -1) break;
}
catch(IOException e) {
exception = e; // Remember for later queries
break;
}
// Prepare the buffer to be drained
buffer.flip();
// Pass it to the writer thread, getting a new drained
// buffer to use in the next pass through the loop.
buffer = exchanger.exchange(buffer);
// If the writer gave us a null buffer, it has exited
// with an IOException, and we should too.
if (buffer == null) return;
}
// When we exit the loop because of EOF or IOExcpetion,
// exchange null with the writer thread, so it will exit.
exchanger.exchange(null);
}
// Exit if we're interrupted, and assume that the writer thread
// will also get interrupted.
catch(InterruptedException abort) {}
}
// We can't throw an exception from run(), but we can remember it
IOException exception;
public IOException getException() { return exception; }
}
// Here is the writer thread
class Writer extends Thread {
public void run() {
try {
ByteBuffer buffer = ByteBuffer.allocateDirect(32 * 1024);
for(;;) {
// Invariant: here we have an empty buffer we need to
// exchange for a full one.
buffer = exchanger.exchange(buffer);
// A null buffer means the reader reached EOF or got
// an IOException. Either way, we're done.
if (buffer == null) return;
// Otherwise, drain the buffer to the destination
while (buffer.hasRemaining()) {
try { destination.write(buffer); }
catch(IOException e) {
// If we get an IO exception, remember it and
// let the reader thread know, so it doesn't
// block forever in exchange()
exception = e;
exchanger.exchange(null);
return;
}
}
// Now make the buffer ready to be refilled so we
// can exchange it again at the top of the loop.
buffer.clear();
}
}
catch(InterruptedException abort) {}
}
IOException exception;
public IOException getException() { return exception; }
}
// Create one thread to read and one thread to write
Reader reader = new Reader();
Writer writer = new Writer();
// Start both threads
reader.start(); writer.start();
// Wait for both threads to stop
try { reader.join(); writer.join(); }
catch(InterruptedException e) {
// If we get interrupted while waiting for the threads to exit,
// then interrupt the threads we spawned.
reader.interrupt(); writer.interrupt();
return;
}
// if either thread encountered an exception, rethrow that exception
if (reader.getException() != null) throw reader.getException();
if (writer.getException() != null) throw writer.getException();
}
Full disclosure time: I've tested this code for copying local files only. I have not exercised the code that handles IOException or InterruptedException. So let me know if you spot any bugs, and don't start calling this method from your production code! If you think this code is nifty, go buy Doug's book. It is well written, and aimed at professional programmers, not academic researchers. And it complements java.util.concurrent very nicely. Finally, if you want to play around with this code, download it here.
April 17, 2004Generics in java.util.concurrentI've been reading up on the new concurrency utilities in java.util.concurrent. Overall, I'm very impressed. But what I think is particularly interesting is how well generic types fit with the "Executors" framework of these utilities. Here's a brief rundown: An ExecutorService executes things, like Runnable objects. Different implementations of ExecutorService can use different threading strategies. One might queue things up for execution on a single shared thread. Another might create a new thread for every Runnable. And another might implement a thread pool. The concurrency utilities also generalize the notion of a Runnable. A Callable is a chunk of code that can return a result or throw an exception (a Runnable can do neither). Callable is parameterized with the type of its return value. So a Callable that does a floating-point computation might be a Callable<Double>. ExecutorService can asynchronously execute Callable objects as well as Runnable objects. Since a Callable returns a result, however, the ExecutorService returns a Future object as a placeholder for the future result of the executing Callable. Like Callable, Future is parameterized with the result type. So, if you call the submit() method of an ExecutorService and pass it a Callable<Double>, the return value of submit() is a Future<Double>. When you're ready to retrieve the result of the computation, you just call the get() method of the Future<Double> object. It blocks, if necessary, until the computation is done, and returns the result. Thanks to auto-unboxing, you don't have to mess with a Double object and can just store this result into a primitive double variable. Future<Double>.... How lovely! An elegant name that captures the meaning of the type. My point, I guess, is that a well-crafted API like this demonstrates that generics are indeed useful beyond their canonical use-case of collections. A related question is how hard is it to create a well-crafted generic API? That is, can ordinary Java programmers (who are not primarily API designers) create APIs that use generics elegantly and effectively? Or will we end up with poorly-designed, hard-to-understand APIs created using generics simply because they were there? Time will tell, I suppose. Bill Dudney wonders about this in his blog. (Ignore his remarks about e-mailing the generics expert group, though. The public-review period has long since ended, and the Tiger release schedule is hard upon the implementors: there is no time for further changes.) And don't forget to read up on concurrency. Its a really sweet package .
|
Advertising
About
Store
Search
Archives
April 2008
March 2008 February 2008 January 2008 November 2007 October 2007 September 2007 August 2007 July 2007 June 2007 May 2007 April 2007 March 2007 February 2007 January 2007 December 2006 November 2006 October 2006 September 2006 August 2006 July 2006 June 2006 May 2006 April 2006 March 2006 January 2006 December 2005 November 2005 October 2005 September 2005 August 2005 July 2005 June 2005 April 2005 March 2005 February 2005 December 2004 October 2004 September 2004 July 2004 June 2004 May 2004 April 2004 March 2004 February 2004 January 2004 Syndicate
|