首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java NIO文件锁和可中断通道【源码笔记】

Java NIO文件锁和可中断通道【源码笔记】

作者头像
瓜农老梁
发布2020-02-18 16:56:53
发布2020-02-18 16:56:53
8370
举报
文章被收录于专栏:瓜农老梁瓜农老梁

一、从一个例子开始

通过以下示例对源码进行跟踪。

代码语言:javascript
复制
File file = new File("/mytest/channletst.tmp");
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
FileChannel fileChannel = randomAccessFile.getChannel();
FileLock fileLock = fileChannel.lock();
代码语言:javascript
复制
public final FileLock lock() throws IOException {
    return lock(0L, Long.MAX_VALUE, false); // @1
}

备注:标记@1中lock()默认对整个文件进行加锁,默认使用独占锁

二、源码流程图

三、源码解读

1.图中第@5:标记I/O开始操作begin()源码
代码语言:javascript
复制
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);
}
2.Thread的interrupt源码
代码语言:javascript
复制
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,释放锁操作。

3.图中第@6:标记I/O结束操作end()源码
代码语言:javascript
复制
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()操作结对出现;根据不同的中断场景抛出不同的异常。

四、Native lock0源码
代码语言:javascript
复制
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构造函数
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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.文件锁
代码语言:javascript
复制
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》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-20,如有侵权请联系 [email protected] 删除

本文分享自 瓜农老梁 微信公众号,前往查看

如有侵权,请联系 [email protected] 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.图中第@5:标记I/O开始操作begin()源码
  • 2.Thread的interrupt源码
  • 3.图中第@6:标记I/O结束操作end()源码
  • 四、Native lock0源码
  • flock64构造函数
  • 备注:@1 F_SETLKW64
  • 备注:@2 F_SETLK64
  • 1.文件锁
  • 2.可中断的通道
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档