Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 5
import com.holub.asynch.Reader_writer; import java.io.*; class Asynchronous_flush { private OutputStream out; private Reader_writer lock = new Reader_writer(); private byte[] buffer; private int length; //... synchronized void flush( ) { new Thread() { public void run() { try { lock.request_write(); out.write( buffer, 0, length ); length = 0; } catch( IOException e ) { // ignore it. } finally { lock.write_accomplished(); } } }.start(); } }
I've wrapped the former contents of the flush() method inside the run() method of an anonymous inner class that extends Thread. Now flush() does nothing but fire off the thread and return. This simple strategy can work for simple situations, but unfortunately it
doesn't work here. Let's analyze the problems one at a time.
The main problem is that the write operation is no longer thread-safe. Simply synchronizing the flush() method locks the object only while we're in the flush() method, which isn't for very long. The actual write() operation is performed on its own thread long after flush() has returned, and the buffer may have been modified several times in the interim (or even worse, may be modified while the
write is in progress). A possible solution to the synchronization problem is to make a copy of the buffer while we're synchronized,
and then work from the copy when inside the (unsynchronized) auxiliary thread. The only time synchronization is necessary
is while we're actually making the copy.
Because it's so easy, it would be nice if we could implement this strategy like this:
synchronized void flush( )
{
byte[] copy = buffer.clone();
length = 0;
new Thread()
{ public void run()
{ try
{ lock.request_write(); out.write( copy, 0, length );
}
catch( IOException e )
{ // ignore it.
}
finally
{ lock.write_accomplished();
}
}
}.start();
}
But this code doesn't even compile. Remember that the inner-class object -- the anonymous Thread derivative -- exists long after the method returns. Consequently, the local variables of the method can't be used by the
thread (unless they're final, which, in this case, they aren't) simply because they won't exist any more; they're destroyed
when flush() returns. We can copy local variables into the thread object, however.
Listing 1 solves most of these problems by using the copy strategy I just discussed. The strange-looking thing on line 24 is an "instance initializer" for the inner class. Think of it syntactically as a static initializer that isn't static --
a sort-of metaconstructor. The code in the instance initializer is effectively copied into all constructors, including the
compiler-generated "default" constructor, above any code specified in the constructor itself. That is, if you have both an
instance initializer and a constructor, the code in the instance initializer executes first. (The one exception to this rule
is that the instance initializer is not copied into any constructor that calls another constructor using the this(optional_args) syntax. This way the code in the instance initializer is executed only once.) The syntax is pretty ugly, but there it is.