当有多个进程或者多个应用同时操作文件时 , 会并行往文件中写入字节 , 如何保证多个进程中文件写入或者操作当原子性就很重要.
此时 , 在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;
}
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);
}
java.sun.nio.ch.FileDispatcherImpl.java
中调用lockint 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;
}