Tải bản đầy đủ - 0 (trang)
Figure 3-8. A disk file with two holes

Figure 3-8. A disk file with two holes

Tải bản đầy đủ - 0trang

If the file resides on a local filesystem, then upon returning from force(), it's guaranteed

that all modifications to the file since the channel was created (or the last call to force())

have been written to disk. This is important for critical operations, such as transaction

processing, to insure data integrity and reliable recovery. However, this guarantee of

synchronization to permanent storage cannot be made if the file resides on a remote

filesystem, such as NFS. The same may be true for other filesystems, depending on

implementation. The JVM can't make promises the operating system or filesystem won't

keep. If your application must maintain data integrity in the face of system failures, verify

that the operating system and/or filesystem you're using are dependable in that regard.

For applications in which confidence in data integrity are essential,

verify the capabilities of the operating environment on which you

plan to deploy.

The boolean argument to force() indicates whether metadata about the file should also

be synchronized to disk before returning. Metadata represents things such as file

ownership, access permissions, last modification time, etc. In most cases, this information

is not critical for data recovery. Passing false to force() indicates that only the file data

need be synchronized before returning. In most cases, synchronizing the metadata will

require at least one additional low-level I/O operation by the operating system. Some

high-volume transactional applications may gain a moderate performance increase

without sacrificing data integrity by not requiring metadata updates on each call to

force().

3.3.2 File Locking

Until 1.4, a feature sorely lacking from the Java I/O model was file locking. While most

modern operating systems have long had file-locking capabilities of one form or another,

file locks have not been available to Java programmers until the JDK 1.4 release. File

locking is essential for integration with many non-Java applications. It can also be

valuable for arbitrating access among multiple Java components of a large system.

As discussed in Chapter 1, locks can be shared or exclusive. The file-locking features

described in this section depend heavily on the native operating-system implementation.

Not all operating systems and filesystems support shared file locks. For those that don't, a

request for a shared lock will be silently promoted to an exclusive-lock request. This

guarantees correctness but may impact performance considerably. For example,

employing only exclusive locks would serialize all the reader processes in Figure 1-7. Be

sure you understand file-locking behavior on the operating system and filesystem(s) on

which you plan to deploy; it could seriously affect your design choices.

Additionally, not all platforms implement basic file locking in the same way. File-locking

semantics may vary between operating systems and even different filesystems on the

same operating system. Some operating systems provide only advisory locking, some

only exclusive locks, and some may provide both. You should always manage file locks

as if they were advisory, which is the safest approach. But it's also wise to be aware of

83



how locks are implemented in the underlying operating system. For example, if all locks

are mandatory, the locks you obtain may impact other applications running on the same

system if you don't release them in a timely manner.

An important caveat regarding the file-locking model implemented by FileChannel is

that locks are applied per file, not per channel or per thread. This means that file locks are

not appropriate for coordinating access between threads in the same JVM.

If one thread acquires an exclusive lock on a given file, and a second thread requests an

exclusive lock for the same file region using an independently opened channel, the

second thread will be granted access. If the two threads are running in different JVMs,

the second thread would block, because locks are ultimately arbitrated by the operating

system or filesystem almost always at the process rather than thread level. Locks are

associated with a file, not with individual file handles or channels.

Locks are associated with files, not channels. Use locks to coordinate

with external processes, not between threads in the same JVM.



File locks are intended for arbitrating file access at the process level, such as between

major application components or when integrating with components from other vendors.

If you need to control concurrent access between multiple Java threads, you may need to

implement your own, lightweight locking scheme. Memory-mapped files (described later

in this chapter) may be an appropriate choice for that case.

Let's take a look at the FileChannel API methods related to file locking:

public abstract class FileChannel

extends AbstractChannel

implements ByteChannel, GatheringByteChannel, ScatteringByteChannel

{

// This is a partial API listing

public final FileLock lock()

public abstract FileLock lock (long position, long size, boolean

shared)

public final FileLock tryLock()

public abstract FileLock tryLock (long position, long size, boolean

shared)

}



This time, let's look first at the form of lock() that takes arguments. Locks are obtained on

regions of files. Calling lock() with arguments specifies the beginning position within

the file where the locked region should begin and the size of the region to lock. The

third argument, shared, indicates whether you want the lock to be shared (true) or

exclusive (false). To obtain a shared lock, you must have opened the file with read



84



permission. Write permission is required for an exclusive lock. The position and size

you provide must be nonnegative.

The lock region does not need to be constrained to the file size; a lock can extend beyond

the end of the file. Therefore, it is possible to lock an area of a file before writing data

there. It's also possible to lock a region that doesn't even overlap any of the file content,

such as beyond the last byte of the file. If the file grows into that region, then your lock

would cover that new area of the file. Conversely, if you lock a region of a file, and the

file grows beyond your locked area, the new file content would not be protected by your

lock.

The simple form of lock(), which takes no arguments, is a convenience method for

requesting an exclusive lock on an entire file, up to the maximum size it can attain. It's

equivalent to:

fileChannel.lock (0L, Long.MAX_VALUE, false);



The lock() method will block if the lock range you are requesting is valid, but it must wait

for a preexisting lock to be released. If your thread is suspended in this situation, it's

subject to interrupt semantics similar to those discussed in Section 3.1.3. If the channel is

closed by another thread, the suspended thread will resume and receive an

AsynchronousCloseException. If the suspended thread is interrupted directly (by calling

its interrupt() method), it will wake with a FileLockInterruptionException. This

exception will also be thrown immediately if the thread's interrupt status is already set

when lock() is invoked.

In the above API listing, the two methods named tryLock() are nonblocking variants of

lock(). They function the same as lock() but return null if the requested lock cannot be

acquired immediately.

As you can see, lock() and tryLock() return a FileLock object. Here is the complete API

of FileLock:

public abstract class FileLock

{

public final FileChannel channel()

public final long position()

public final long size()

public final boolean isShared()

public final boolean overlaps (long position, long size)

public abstract boolean isValid();

public abstract void release() throws IOException;

}



The FileLock class encapsulates a locked file region. FileLock objects are created by

FileChannel objects and are always associated with that specific channel instance. You

can query a lock object to determine which channel created it by calling the channel()

method.



85



A FileLock object is valid when created and remains so until its release() method is

called, the channel it's associated with is closed, or the JVM shuts down. The validity of a

lock can be tested by invoking its isValid() boolean method. A lock's validity may change

over time, but its other properties — position, size, and exclusivity — are set at creation

time and are immutable.

You can test a lock to determine if it is shared or exclusive by invoking isShared(). If

shared locks are not supported by the underlying operating system or filesystem, this

method will always return false, even if you passed true when requesting the lock. If

your application depends on shared-locking behavior, test the returned lock to be sure

you got the type you requested. FileLock objects are thread-safe; multiple threads may

access a lock object concurrently.

Finally, a FileLock object can be queried to determine if it overlaps a given file region by

calling its overlaps() method. This will let you quickly determine if a lock you hold

intersects with a region of interest. A return of false does not guarantee that you can

obtain a lock on the desired region. One or more locks may be held elsewhere in the JVM

or by external processes. Use tryLock() to be sure.

Although a FileLock object is associated with a specific FileChannel instance, the lock it

represents is associated with an underlying file, not the channel. This can cause conflicts,

or possibly deadlocks, if you don't release a lock when you're finished with it. Carefully

manage file locks to avoid such problems. Once you've successfully obtained a file lock,

be sure to release it if subsequent errors occur on the channel. A code pattern similar to

the following is recommended:

FileLock lock = fileChannel.lock()

try {



} catch (IOException) [



} finally {

lock.release()

}



The code in Example 3-3 implements reader processes using shared locks and writers

using exclusive locks, as illustrated in Figures Figure 1-7 and Figure 1-8. Because locks

are associated with processes and not with Java threads, you will need to run multiple

copies of this program. Start one writer and two or more readers to see how the different

types of locks interact with each other.

Example 3-3. Shared- and exclusive-lock interaction

package com.ronsoft.books.nio.channels;

import java.nio.ByteBuffer;

import java.nio.IntBuffer;

import java.nio.channels.FileChannel;



86



import java.nio.channels.FileLock;

import java.io.RandomAccessFile;

import java.util.Random;

/**

* Test locking with FileChannel.

* Run one copy of this code with arguments "-w /tmp/locktest.dat"

* and one or more copies with "-r /tmp/locktest.dat" to see the

* interactions of exclusive and shared locks. Note how too many

* readers can starve out the writer.

* Note: The filename you provide will be overwritten. Substitute

* an appropriate temp filename for your favorite OS.

*

* Created April, 2002

* @author Ron Hitchens (ron@ronsoft.com)

*/

public class LockTest

{

private static final int SIZEOF_INT = 4;

private static final int INDEX_START = 0;

private static final int INDEX_COUNT = 10;

private static final int INDEX_SIZE = INDEX_COUNT * SIZEOF_INT;

private ByteBuffer buffer = ByteBuffer.allocate (INDEX_SIZE);

private IntBuffer indexBuffer = buffer.asIntBuffer();

private Random rand = new Random();

public static void main (String [] argv)

throws Exception

{

boolean writer = false;

String filename;

if (argv.length != 2) {

System.out.println ("Usage: [ -r | -w ] filename");

return;

}

writer = argv [0].equals ("-w");

filename = argv [1];

RandomAccessFile raf = new RandomAccessFile (filename,

(writer) ? "rw" : "r");

FileChannel fc = raf.getChannel();

LockTest lockTest = new LockTest();

if (writer) {

lockTest.doUpdates (fc);

} else {

lockTest.doQueries (fc);

}

}

// ---------------------------------------------------------------// Simulate a series of read-only queries while



87



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Figure 3-8. A disk file with two holes

Tải bản đầy đủ ngay(0 tr)

×