专栏首页Android相关跨进程文件锁 - FileChannel

跨进程文件锁 - FileChannel

背景

当有多个进程或者多个应用同时操作文件时 , 会并行往文件中写入字节 , 如何保证多个进程中文件写入或者操作当原子性就很重要.

此时 , 在Java层可以使用FileChannel.lock来完成多进程之间对文件操作的原子性 , 而该lock会调用Linux的fnctl来从内核对文件进行加锁

源码

  1. 通过File.getChannel.lock()将文件加锁
RandomAccessFile file;
file.getChannel().lock();
  1. getChannel中 , 调用FileChannelImpl.open打开文件
public final FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, true, rw, this);
            }
            return channel;
        }
    }
  1. open函数中会创建FileDispathcerImpl对象 , 后续会使用它进行加锁
public static FileChannel open(FileDescriptor fd, String path,
                                   boolean readable, boolean writable,
                                   Object parent)
    {
        return new FileChannelImpl(fd, path, readable, writable, false, parent);
    }

private FileChannelImpl(FileDescriptor fd, String path, boolean readable,
                            boolean writable, boolean append, Object parent)
    {
        this.fd = fd;
        this.readable = readable;
        this.writable = writable;
        this.append = append;
        this.parent = parent;
        this.path = path;
        this.nd = new FileDispatcherImpl(append);
        // Android-changed: Add CloseGuard support.
        if (fd != null && fd.valid()) {
            guard.open("close");
        }
    }

4.在调用lock函数后 , 开始调用native方法锁住文件

public FileLock lock(long position, long size, boolean shared)
        throws IOException
    {
        // 确认文件已经打开 , 即判断open标识位
        ensureOpen();
        if (shared && !readable)
            throw new NonReadableChannelException();
        if (!shared && !writable)
            throw new NonWritableChannelException();
        // 创建FileLock对象
        FileLockImpl fli = new FileLockImpl(this, position, size, shared);
        // 创建FileLockTable对象
        FileLockTable flt = fileLockTable();
        flt.add(fli);
        boolean completed = false;
        int ti = -1;
        try {
            // 标记开始IO操作 , 可能会导致阻塞
            begin();
            ti = threads.add();
            if (!isOpen())
                return null;
            int n;
            do {
                // 开始锁住文件
                n = nd.lock(fd, true, position, size, shared);
            } while ((n == FileDispatcher.INTERRUPTED) && isOpen());
            if (isOpen()) {
                // 如果返回结果为RET_EX_LOCK的话
                if (n == FileDispatcher.RET_EX_LOCK) {
                    assert shared;
                    FileLockImpl fli2 = new FileLockImpl(this, position, size,
                                                         false);
                    flt.replace(fli, fli2);
                    fli = fli2;
                }
                completed = true;
            }
        } finally {
            // 释放锁
            if (!completed)
                flt.remove(fli);
            threads.remove(ti);
            try {
                end(completed);
            } catch (ClosedByInterruptException e) {
                throw new FileLockInterruptionException();
            }
        }
        return fli;
    }
  1. 创建FileLockTable
private FileLockTable fileLockTable() throws IOException {
        if (fileLockTable == null) {
            synchronized (this) {
                if (fileLockTable == null) {
                    // 判断系统属性sun.nio.ch.disableSystemWideOverlappingFileLockCheck
                    // 是否支持共享文件
                    if (isSharedFileLockTable()) {
                        int ti = threads.add();
                        try {
                            ensureOpen();
                            // 创建fileLockTable对象
                            fileLockTable = FileLockTable.newSharedFileLockTable(this, fd);
                        } finally {
                            threads.remove(ti);
                        }
                    } else {
                        fileLockTable = new SimpleFileLockTable();
                    }
                }
            }
        }
        return fileLockTable;
    }
  1. 调用begin方法 , 设置中断触发
protected final void begin() {
        if (interruptor == null) {
            interruptor = new Interruptible() {
                    public void interrupt(Thread target) {
                        synchronized (closeLock) {
                            if (!open)
                                return;
                            open = false;
                            interrupted = target;
                            try {
                                AbstractInterruptibleChannel.this.implCloseChannel();
                            } catch (IOException x) { }
                        }
                    }};
        }
        blockedOn(interruptor);
        Thread me = Thread.currentThread();
        if (me.isInterrupted())
            interruptor.interrupt(me);
    }
  1. java.sun.nio.ch.FileDispatcherImpl.java中调用lock
int lock(FileDescriptor fd, boolean blocking, long pos, long size,
             boolean shared) throws IOException
    {
        BlockGuard.getThreadPolicy().onWriteToDisk();
        return lock0(fd, blocking, pos, size, shared);
    }
  1. FileDispatcherImpl.c文件中
JNIEXPORT jint JNICALL
FileDispatcherImpl_lock0(JNIEnv *env, jobject this, jobject fdo,
                                      jboolean block, jlong pos, jlong size,
                                      jboolean shared)
{
    // 通过fdval函数找到fd
    jint fd = fdval(env, fdo);
    jint lockResult = 0;
    int cmd = 0;
    // 创建flock对象
    struct flock64 fl;

    fl.l_whence = SEEK_SET;
    // 从position位置开始
    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;
    }
    // 设置锁参数
    // F_SETLK : 给当前文件上锁(非阻塞)。
    // F_SETLKW : 给当前文件上锁(阻塞,若当前文件正在被锁住,该函数一直阻塞)。
    if (block == JNI_TRUE) {
        cmd = F_SETLKW64;
    } else {
        cmd = F_SETLK64;
    }
    // 调用fcntl锁住文件
    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;
}

参考资料

Unix中fcntl实现对文件加锁功能

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java线程池---getTask方法解析

    /** * Performs blocking or timed wait for a task, depending on * current confi...

    None_Ling
  • Linux编程--地址计算

    在学习Matrix的ELF Hook的过程中,发现在查找Library基址指针的时候,对于指针的运算有一些疑惑,特此记录。

    None_Ling
  • 为什么使用Cmake编译出来的So只有arm64-v8a架构

    为何在使用Cmake编译JNI文件的时候,运行的时候,只会生成arm64-v8a架构的So打入Apk中,而并不会生成所有架构,如x86,mips等。

    None_Ling
  • Java每日一练(2017/6/5)

    题目要求 本期题目: 请实现一个单例模式! 数字推理题: 5,7,2,10,-1,13,() 括号里面的数字应该是几? 读者可在右下角的留言留下你(逻辑思维...

    Java学习
  • MongoDB中删除document的方法

    > db.users.remove({z:'abc'}) 删除记录 delete from users where z="abc" 默认remove没有带选项t...

    二狗不要跑
  • 测试一下StringBuffer和StringBuilder及字面常量拼接三种字符串的效率

    之前一篇里写过字符串常用类的三种方式《java中的字符串相关知识整理》,只不过这个只是分析并不知道他们之间会有多大的区别,或者所谓的StringBuffer能提...

    用户1105954
  • 机房收费系统-状态图与活动图

       用来描述对象,子系统,系统的生命周期。通过状态图可以了解一个对象所能达到的所有状态,以及对象收到的事件对对象状态的影响。

    令仔很忙
  • OpenDaylight与Mininet应用实战之基本环境搭建一

    简要介绍在没有OpenFlow硬件设备下如何搭建一个OpenFlow环境。控制器使用OpenDaylight(以下简写为ODL),是现在主流的控制器项目,功能比...

    SDNLAB
  • PHP5.4+Apache2.2+Mysql5.0+PHPMyAdmin3.2.5安装配置

    落叶大大
  • React中组件通信的几种方式

    React数据流动是单向的,父组件向子组件通信也是最常见的;父组件通过props向子组件传递需要的信息 Child.jsx

    木子星兮

扫码关注云+社区

领取腾讯云代金券