背景
當有多個程序或者多個應用同時操作檔案時 , 會并行往檔案中寫入位元組 , 如何保證多個程序中檔案寫入或者操作當原子性就很重要.
此時 , 在Java層可以使用
FileChannel.lock
來完成多程序之間對檔案操作的原子性 , 而該
lock
會調用Linux的
fnctl
來從核心對檔案進行加鎖
源碼
- 通過
将檔案加鎖File.getChannel.lock()
RandomAccessFile file;
file.getChannel().lock();
複制
- 在
中 , 調用getChannel
打開檔案FileChannelImpl.open
public final FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, true, rw, this);
}
return channel;
}
}
複制
-
函數中會建立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;
}
複制
- 建立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;
}
複制
- 調用
方法 , 設定中斷觸發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);
}
複制
- 在
中調用lockjava.sun.nio.ch.FileDispatcherImpl.java
int lock(FileDescriptor fd, boolean blocking, long pos, long size,
boolean shared) throws IOException
{
BlockGuard.getThreadPolicy().onWriteToDisk();
return lock0(fd, blocking, pos, size, shared);
}
複制
- 在
檔案中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實作對檔案加鎖功能