最近碰到一个项目,有多个进程,同时操作同一目录的同一文件,笔者使用java语言。由于文件比较小,所以上线后并没有碰到什么问题。但是,我不禁想到一些问题:不同进程对同一个文件进行操作,如何保证数据的正确性。
如果在同一进程之内,我完全可以在写文件的时候,加一把对象锁,同一时刻,只能有一个线程写文件。but,我的问题是不同进程之间如何保证。
于是,我找到一个东西FileLock,关于FileLock我做了一些测试,最终也并没有达到我要的效果。希望牛人们能提供点思路,帮我解惑。
我写了一个测试类,分两次给文件写入1111111和222222,在写之前获取一把文件锁,程序结束,在finally关闭掉锁。
public static void main(String[] args) {
FileOutputStream fos = null;
FileLock fLock = null;
try {
System.out.println("starting...");
fos = new FileOutputStream(PATH);
fLock = fos.getChannel().lock();
fos.write("1111111".getBytes());
fos.flush();
fos.write("2222222".getBytes());//此处加了一个断点
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fLock != null)
try {
fLock.release();
} catch (IOException e) {
e.printStackTrace();
}
if (fos != null)
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
到这里来看,似乎都没有什么问题。
现在,我用nodepad++打开这个文件,首先,内容已经没有了(我想大概是因为没有获取到文件锁,所以无法读取内容吧)
but,让我无法理解的地方来了,我在里面写入内容,然后保存,nodepad没有报任何异常(我想,大概是因为他自己捕捉了异常吧)
然后,我再用windows自带的文本工具打开,看到的内容是一片空白!!!!!!(到这里我已经无法理解了)
然后,有回到eclipse工程,在刚刚断点的地方,我直接让程序跑完,程序没有报出任何异常,在用windows自带的工具打开,看到内容如下:
到这里来看,似乎都没有什么问题。
现在,我用nodepad++打开这个文件,首先,内容已经没有了(我想大概是因为没有获取到文件锁,所以无法读取内容吧)
but,让我无法理解的地方来了,我在里面写入内容,然后保存,nodepad没有报任何异常(我想,大概是因为他自己捕捉了异常吧)
然后,我再用windows自带的文本工具打开,看到的内容是一片空白!!!!!!(到这里我已经无法理解了)
然后,有回到eclipse工程,在刚刚断点的地方,我直接让程序跑完,程序没有报出任何异常,在用windows自带的工具打开,看到内容如下:
从这个结果可以看出,之前写入1111111的地方,被"脏写"了。
所以,我想在不同进程之间,对文件的写操作进行保护,看来是失败了。
之后,我又做了几个尝试:
1.同样两个程序,都是用java跑的,是两个进程,第一个程序就是上面的代码里的,也同样,在上面断点;第二个程序就是直接写文件。神奇的事情又发生了,第二程序确实会抛出异常,说另一个程序正在使用,but,文件里面的内容也是被“脏写”了。最后释放断点,最后得到的结果跟上面一样。
2.两个程序,第一个用java写,更上面一样,第二程序用c++写,调试方法和上面一样,得到相同的实验结果。
那么我考虑,java这个FileLock是否是对文件的内存映射进行加锁,想了下,不对,因为如果是这样,那么这个锁仅仅在本个java进程有效;
如果是直接锁文件呢?想了下,也不太可能,windows上似乎并没有这种机制;
再来,我请教一个大牛,大牛说,windows对于文件的操作,是有一个文件映射的句柄,或者说文件句柄,所有的操作都是对这个句柄进行操作的,好吧,我也很疑惑。
所以,综上所述,就是没有解决我的问题,各位大神有啥解决思路没有?
------------------------------------------------------------------------------------------------------------------------------------------------------
先假设上面的问题不存在,FileLock是一个很完美的锁禁止,并且以下的尝试都用java语言,以下是我对FileLock的一些理解:
另外,在使用FileLock的时候,有一个lock(int start,int size,boolean isShare)的方法,带参数的哟。关于这个start和size就感觉非常奇怪,这个又是锁的啥,难道文件还是锁指定范围?答案是否定的。
首先说,FileLock有两种锁:
排他锁:只有当前线程可以读写,其他的线程或者进程对这个进行读或者写都会抛出异常
共享锁:当前线程可以操作,但是,要用RandomAccessFile并且要"rw",否则会抛异常,不信,你可以试试. 其他线程或者进程,可以读取。在锁住的时候,写,只能自己写,别人只能读
然后来说这个start和size,这个,似乎又是锁的channel,只对同一进程有效:
lock = channel.lock(0, 4, true);
lock = channel.lock(4, 8, true);
lock = channel.lock(0, 4, true);
lock = channel.lock(3, 8, true);
再有就是,我同样的程序获得锁的地方加了一个断点,然后做最开始的那些测试,实验结果一样,不好。
然后神奇的事情又发发生了,我用下面的代码:
FileOutputStream fos = null;
FileLock fLock = null;
try {
System.out.println("starting...");
fos = new FileOutputStream(PATH);
fLock = fos.getChannel().lock();
fos.write("1111111".getBytes());
fos.flush();
fos.write("2222222".getBytes());
fos.flush();
}..........
but,我再用这段代码:
FileLock lock = null;
FileChannel channel = null;
try {
RandomAccessFile raf = new RandomAccessFile(new File(FileLocking.PATH),"rw");
raf.seek(4);
channel = raf.getChannel();
lock = channel.lock(0, 4, true);
System.out.println("2222");
}........
好吧,继续一万点的暴击伤害。
-------------------------------------------------------------------------------------------------------------------
综上所述,我觉得,应该是我错误的使用了FileLock,请各位大牛指点批评。最主要的疑惑点在于:
1.不同进程之间,如果使用文件锁;
2.FileLock带参数的lock如何正确的使用,以及参数的含义
3.FileLock的lock到底锁的是什么对象,是否带参数的lock和不带参数的lock锁的是不同的对象
基于上面碰到的问题,如果真的要我使用文件锁的话,我会这么做:
1.在写文件的时候,尽量使用排他锁,也就是直接使用不带参数的lock或者tryLock,这是读文件的时候,如果有异常应该捕获,下次重读。那么也要求,写文件不能太频繁。
2.多个进程操作文件的时候,尽量不要操作同一个文件,否则有些不可预见的错误(是我自己知识面不够吧)