一、从一个例子开始
通过以下示例对源码进行跟踪。
File file = new File("/mytest/channletst.tmp");
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
FileChannel fileChannel = randomAccessFile.getChannel();
FileLock fileLock = fileChannel.lock();public final FileLock lock() throws IOException {
return lock(0L, Long.MAX_VALUE, false); // @1
}备注:标记@1中lock()默认对整个文件进行加锁,默认使用独占锁
二、源码流程图

三、源码解读
protected final void begin() {
if (interruptor == null) {
// 实现Interruptible接口
interruptor = new Interruptible() {
// 具体实现的方法接受参数为Thread
public void interrupt(Thread target) {
synchronized (closeLock) {
// 通道未开启直接返回
if (!open)
return;
open = false;
interrupted = target;
try {
// 释放锁关闭通道操作 AbstractInterruptibleChannel.this.implCloseChannel();
} catch (IOException x) { }
}
}};
}
// 将interruptor赋值给当前线程Thread的成员变量Interruptible blocker
// 以便线程中断时回调
blockedOn(interruptor);
Thread me = Thread.currentThread();
// 线程被中断调用
if (me.isInterrupted())
interruptor.interrupt(me);
}public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
// 注入的Interruptible
Interruptible b = blocker;
if (b != null) {
interrupt0();
// 此处回调Interruptible#interrupt方法
b.interrupt(this);
return;
}
}
interrupt0();
}小结:begin()操作即可中断线程的实现过程,在当前线程中注入Interruptible实例,当线程中断时对Interruptible进行回调;回调实现了关闭channel,释放锁操作。
protected final void end(boolean completed)
throws AsynchronousCloseException{
// 当前线程Thread的成员变量Interruptible blocker设置为null
blockedOn(null);
Thread interrupted = this.interrupted;
// 当前线程被中断抛出ClosedByInterruptException
if (interrupted != null && interrupted == Thread.currentThread()) {
interrupted = null;
throw new ClosedByInterruptException();
}
// I/O操作未完成而Channel通道被别的线程关闭
// 抛出AsynchronousCloseException
if (!completed && !open)
throw new AsynchronousCloseException();
}小结:end()操作标记I/O的结束,与begin()操作结对出现;根据不同的中断场景抛出不同的异常。
Java_sun_nio_ch_FileDispatcherImpl_lock0(JNIEnv *env, jobject this, jobject fdo,
jboolean block, jlong pos, jlong size,
jboolean shared)
{
jint fd = fdval(env, fdo);
jint lockResult = 0;
int cmd = 0;
struct flock64 fl;
// l_start从文件头开始计算偏移值
fl.l_whence = SEEK_SET;
// 设置被加锁的长度
if (size == (jlong)java_lang_Long_MAX_VALUE) {
fl.l_len = (off64_t)0;
} else {
fl.l_len = (off64_t)size;
}
// 设置锁开始位置
fl.l_start = (off64_t)pos;
// 设置共享锁还是独占锁
if (shared == JNI_TRUE) {
// 设置共享锁
fl.l_type = F_RDLCK;
} else {
// 设置独占锁
fl.l_type = F_WRLCK;
}
if (block == JNI_TRUE) {
cmd = F_SETLKW64; // @1
} else {
cmd = F_SETLK64; // @2
}
lockResult = fcntl(fd, cmd, &fl);
if (lockResult < 0) {
if ((cmd == F_SETLK64) && (errno == EAGAIN || errno == EACCES))
return sun_nio_ch_FileDispatcherImpl_NO_LOCK;
if (errno == EINTR)
return sun_nio_ch_FileDispatcherImpl_INTERRUPTED;
JNU_ThrowIOExceptionWithLastError(env, "Lock failed");
}
return 0;
}struct flock64 {
short l_type; // 锁类型
short l_whence; // 决定l_start锁开始位置
__kernel_loff_t l_start; // 锁定的位置
__kernel_loff_t l_len; // 锁定的长度
__kernel_pid_t l_pid; // 加锁的进程Pid
__ARCH_FLOCK64_PAD
};Sets or clears a file segment lock on a large file; however, if a shared or exclusive lock is blocked by other locks, fcntl() waits until the request can be satisfied. See File Locking for details. You must specify a third argument of type struct flock64 *. When you develop in C-based languages, it is necessary to compile the function with the _LARGE_FILE_API macro defined to use this symbol.Sets or clears a file segment lock for a large file. You must specify a third argument of type struct flock64 *. See File Locking for details. fcntl() returns 0 if it successfully clears the lock. When you develop in C-based languages, it is necessary to compile the function with the _LARGE_FILE_API macro defined to use this symbol.小结:F_SETLKW64与F_SETLK64作用相同;区别在于F_SETLKW64会阻塞。
五、总结
public final FileLock lock()
public abstract FileLock lock(long position, long size, boolean shared)
public abstract FileLock tryLock(long position, long size, boolean shared)
public final FileLock tryLock()总结:lock()与tryLock的区别在于,lock会阻塞调用Native API为F_SETLKW64;tryLock为非阻塞,调用Native API为F_SETLK64;锁与文件关联,而不是线程和通道,是进程级别的判优访问。
总结:可中断的通道((Interruptible)实现InterruptibleChannel接口,可以被异步关闭(即另外线程调用该线程的的interrupt()方法);实现原理即文中的标记I/O开始的begin()和标记I/O结束的end()。
六、参考资料
《Java NIO》