天天看點

跨程序檔案鎖 - 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實作對檔案加鎖功能