目录
一、从一个例子开始
二、源码流程图
三、源码解读
1.图中第@5:标记I/O开始操作begin()源码
2.Thread的interrupt源码
3.图中第@6:标记I/O结束操作end()源码
四、Native lock0源码
五、总结
1.文件锁
2.可中断的通道
六、参考资料
七、系列文章
一、从一个例子开始
通过以下示例对源码进行跟踪。
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()默认对整个文件进行加锁,默认使用独占锁
二、源码流程图
三、源码解读
1.图中第@5:标记I/O开始操作begin()源码
protected final void begin() {if (interruptor == null) {// 实现Interruptible接口interruptor = new Interruptible() {// 具体实现的方法接受参数为Threadpublic 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);}
2.Thread的interrupt源码
public void interrupt() {if (this != Thread.currentThread())checkAccess();synchronized (blockerLock) {// 注入的InterruptibleInterruptible b = blocker;if (b != null) {interrupt0();// 此处回调Interruptible#interrupt方法b.interrupt(this);return;}}interrupt0();}
小结:begin()操作即可中断线程的实现过程,在当前线程中注入Interruptible实例,当线程中断时对Interruptible进行回调;回调实现了关闭channel,释放锁操作。
3.图中第@6:标记I/O结束操作end()源码
protected final void end(boolean completed)throws AsynchronousCloseException{// 当前线程Thread的成员变量Interruptible blocker设置为nullblockedOn(null);Thread interrupted = this.interrupted;// 当前线程被中断抛出ClosedByInterruptExceptionif (interrupted != null && interrupted == Thread.currentThread()) {interrupted = null;throw new ClosedByInterruptException();}// I/O操作未完成而Channel通道被别的线程关闭// 抛出AsynchronousCloseExceptionif (!completed && !open)throw new AsynchronousCloseException();}
小结:end()操作标记I/O的结束,与begin()操作结对出现;根据不同的中断场景抛出不同的异常。
四、Native lock0源码
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;}
flock64构造函数
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};
备注:@1 F_SETLKW64
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.
备注:@2 F_SETLK64
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会阻塞。
五、总结
1.文件锁
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;锁与文件关联,而不是线程和通道,是进程级别的判优访问。
2.可中断的通道
总结:可中断的通道((Interruptible)实现InterruptibleChannel接口,可以被异步关闭(即另外线程调用该线程的的interrupt()方法);实现原理即文中的标记I/O开始的begin()和标记I/O结束的end()。
六、参考资料
《Java NIO》
七、系列文章
「瓜农老梁 学习同行」