前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >跨进程文件锁 - FileChannel

跨进程文件锁 - FileChannel

作者头像
None_Ling
发布2020-09-17 17:28:26
1.5K0
发布2020-09-17 17:28:26
举报
文章被收录于专栏:Android相关Android相关Android相关

背景

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

此时 , 在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实现对文件加锁功能

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 源码
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档