最近碰到一個項目,有多個進程,同時操作同一目錄的同一文件,筆者使用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();
}
}
}
注意,以上代碼標註的地方,我加了一個斷點。然後我發現,用windows自帶的文本編輯工具,當改變文件內容的時候會報錯:
到這裏來看,似乎都沒有什麼問題。
現在,我用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);
然後,這樣是ok的
lock = channel.lock(0, 4, true);
lock = channel.lock(3, 8, true);
但是這樣就不ok了。然後api裏面提供了一個overlaps的方法,我做了一些實驗,也沒發現有啥效果。
再有就是,我同樣的程序獲得鎖的地方加了一個斷點,然後做最開始的那些測試,實驗結果一樣,不好。
然後神奇的事情又發發生了,我用下面的代碼:
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();
}..........
確實,會在獲取lock的地方,阻塞,等待鎖。
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.多個進程操作文件的時候,儘量不要操作同一個文件,否則有些不可預見的錯誤(是我自己知識面不夠吧)