Why is file locking necessary?
When reading and writing to a file, an operating
system maintains a file pointer that
is the offset from the start of the file where the
next read or write operation will occur.
There can be separate file pointers for reading and writing.
Each time a process opens a file, the operating system
associates a file pointer for reading and writing with
that process. If another process opens the same file,
then another set of file pointers are created. Ever opened
file has its own file pointers.
Since the file pointers are not shared, two processes
can write to the same area of a file, the last process to
write will win. This means that the data written
by the first process will be lost.
Loss of data demonstration
The NoFileLocking
class illustrates
how data can be lost when multiple processes write
to the same file at the same time.
import java.io.*;
public class NoFileLocking {
public static void main( String[] args ) {
int delay = 1000;
int len = 5;
String id = args[0];
String file = args[1];
String what = args[2];
try {
RandomAccessFile fw = new RandomAccessFile( file, "rws" );
fw.seek( fw.length() );
for( int i = 0; i < len ; i++ ) {
Thread.sleep( delay );
for( int j = 0; j < what.length(); j++ ) {
fw.write( (byte)what.charAt(j) );
}
System.out.println( id + " " + fw.getFilePointer() );
}
fw.close();
}
catch( Exception ex ) {
}
}
}
Thread.sleep
ensures that multiple
processes will write to the same file at the same time.
Loss of data demonstration (1)
Two terminal windows are needed to demonstrate the data lose.
In one window type the command:
java NoFileLocking A foo a
In the second window, type the following command after two seconds.
java NoFileLocking B foo b
Loss of data demonstration (2)
The output of the first command is:
The second command produces:
Notice that process A and process B overlap with
file pointers 3, 4, and 5.
The contents of foo is:
Which process won? Why?
If the processes did not overlap, what should the contents of
foo be?
Preventing shared access
The data is lost because two processes are writing
to the same file using different sets of file pointers.
The above problem can be avoided by preventing
shared access. Java provide file locking that ensures
that only one process can access a file at a time.
The modified example with file locking is:
import java.io.*;
import java.nio.channels.*;
public class FileLocking {
public static void main( String[] args ) {
int delay = 1000;
int len = 5;
String id = args[0];
String file = args[1];
String what = args[2];
try {
RandomAccessFile fw = new RandomAccessFile( file, "rws" );
FileChannel chan = fw.getChannel();
FileLock lock = chan.lock();
fw.seek( fw.length() );
for( int i = 0; i < len ; i++ ) {
Thread.sleep( delay );
for( int j = 0; j < what.length(); j++ ) {
fw.write( (byte)what.charAt(j) );
}
System.out.println( id + " " + fw.getFilePointer() );
}
fw.close();
lock.release();
}
catch( Exception ex ) {
// XXX what is wrong here
}
}
}
Repeating the previouse experiment
The output from process A with the command
java FileLocking A bar a is:
The output for the command java FileLocking B bar b is:
The output from B does not start until A is finished, and has
released the lock.
The contents of bar are:
No data has been lost, but are there any problems with
this approach?
Java file locking API
The JDK 1.4 version of Java provides file locking in
the java.nio.channels
package.
A FileChannel
object is required
to perform file locking. A file channel object can
be accessed with the getChannel
method
defined in:
java.io.FileInputstream
java.io.FileOutputStream
java.io.RandomAccessFile
A file is locked with FileLock lock()
.
If lock()
is invoked and the file
is already locked, then lock()
will block.
FileLock tryLock()
attempts to
lock a file and will return null
if the
file is already locked. tryLock
is used
if the process should not be blocked.
The FileLock
provides the
release()
method to release the lock.
Once the lock is released, one of the processed (if any) waiting
for the lock will be awoken and given the lock.