[ Team LiB ] Previous Section Next Section

6.1 Locking Files

In addition to enabling high-performance I/O, the FileChannel class also enables file locking, a feature that is not available through the java.io package. Example 6-1 demonstrates how it can be used to prevent two instances of the same application from running at the same time. When the program starts, it attempts to obtain an exclusive write lock on a temporary file. If it succeeds, it just sleeps for 10 seconds to simulate a real application. If it fails, it prints a message saying that another instance of the application is running.

The example also demonstrates simple write and read operations on the file using a ByteBuffer. If the program obtains a write lock on the file, it writes a timestamp 10 seconds in the future into the file (this is the approximate time when the program will stop sleeping and exit). If the program does not obtain a lock on the file, it attempts to read the file anyway. If the underlying operating system enforces locking, this read attempt will fail because another instance of the application holds the lock. Many operating systems, however, provide only advisory locking in which all applications must cooperatively use file-locking calls in order to prevent concurrent access. On an operating system, or Java implementation that performs only advisory locking, the program will be allowed to read the file contents even if another instance of the program has an exclusive lock. In this case, the application reads the timestamp written by the original instance of the application and lets the user know how much longer the original instance will continue to run. Note that if two instances of the program are started at the same time, it is possible for one to read the timestamp file before the other has finished writing it. This would cause corrupt data to be read and could cause spurious output. Since the output is only in an informative error message, we are willing to accept this possibility in this example.

One important thing to understand about file locking with FileChannel is that the locks are held on a per-VM basis, not a per-thread basis. This means that if multiple threads want to access the same file, you must use some other mechanism to prevent concurrent access.

The code for writing and reading the files in this example is relatively simple. We'll see many more detailed examples later in the chapter. The FileChannel has write( ) and read( ) methods that write data from a ByteBuffer and read data into a ByteBuffer. In addition to these methods, you should also note the allocate( ) factory method, used to allocate the ByteBuffer objects, and the putLong( ) and getLong( ) methods for storing and retrieving a long timestamp into and from a ByteBuffer. Finally, note that when a buffer is first allocated, it is empty and is ready to receive data via calls like putLong( ) or read( ). Once data is in the buffer, it is then extracted by calls like write( ) or getLong( ). Every buffer has a current position, and when our code stops filling the buffer and gets ready to drain it, it must call the flip( ) method to reset that current position to the beginning of the buffer. Read the javadoc documentation of the java.nio.Buffer class for complete details.

Example 6-1. Lock.java
package je3.nio;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;

 * Lock.java: this class demonstrates file locking and simple file read and
 * write operations using java.nio.channels.FileChannel.  It uses file locking
 * to prevent two instances of the program from running at the same time.
public class Lock {
    public static void main(String args[  ])
        throws IOException, InterruptedException
        RandomAccessFile file = null; // The file we'll lock
        FileChannel f = null;         // The channel to the file
        FileLock lock = null;         // The lock object we hold

        try {   // The finally clause closes the channel and releases the lock
            // We use a temporary file as the lock file.
            String tmpdir = System.getProperty("java.io.tmpdir");
            String filename = Lock.class.getName( )+ ".lock";
            File lockfile = new File(tmpdir, filename);
            // Create a FileChannel that can read and write that file.
            // Note that we rely on the java.io package to open the file,
            // in read/write mode, and then just get a channel from it.
            // This will create the file if it doesn't exit.  We'll arrange
            // for it to be deleted below, if we succeed in locking it.
            file = new RandomAccessFile(lockfile, "rw");
            f = file.getChannel( );
            // Try to get an exclusive lock on the file.
            // This method will return a lock or null, but will not block.
            // See also FileChannel.lock( ) for a blocking variant.
            lock = f.tryLock( );
            if (lock != null) {  
                // We obtained the lock, so arrange to delete the file when
                // we're done, and then write the approximate time at which 
                // we'll relinquish the lock on the file.
                lockfile.deleteOnExit( );  // Just a temporary file

                // First, we need a buffer to hold the timestamp
                ByteBuffer bytes = ByteBuffer.allocate(8); // a long is 8 bytes

                // Put the time in the buffer and flip to prepare for writing
                // Note that many Buffer methods can be "chained" like this.
                bytes.putLong(System.currentTimeMillis( ) + 10000).flip( );

                f.write(bytes); // Write the buffer contents to the channel
                f.force(false); // Force them out to the disk
            else {
                // We didn't get the lock, which means another instance is
                // running.  First, let the user know this.
                System.out.println("Another instance is already running");
                // Next, we attempt to read the file to figure out how much
                // longer the other instance will be running.  Since we don't
                // have a lock, the read may fail or return inconsistent data.
                try {
                    ByteBuffer bytes = ByteBuffer.allocate(8);
                    f.read(bytes);  // Read 8 bytes from the file
                    bytes.flip( );   // Flip buffer before extracting bytes
                    long exittime = bytes.getLong( ); // Read bytes as a long
                    // Figure out how long that time is from now and round
                    // it to the nearest second.
                    long secs = (exittime-System.currentTimeMillis( )+500)/1000;
                    // And tell the user about it.
                    System.out.println("Try again in about "+secs+" seconds");
                catch(IOException e) {
                    // This probably means that locking is enforced by the OS
                    // and we were prevented from reading the file.

                // This is an abnormal exit, so set an exit code.
            // Simulate a real application by sleeping for 10 seconds.
        finally {  
            // Always release the lock and close the file
            // Closing the RandomAccessFile also closes its FileChannel.
            if (lock != null && lock.isValid( )) lock.release( );
            if (file != null) file.close( );
    [ Team LiB ] Previous Section Next Section