文件鎖
在解決Bug的過程中,遇到過這種問題:就是文件正在使用的過程,從文件管理器里面將文件刪除,這樣可能會導致一些不可預料的問題。在查閱了Java中File類的相關函數之后,在windows下面,可以使用File.rename()或 File.delete(),但是在Linux下面,這種方法也不行,文件還是被直接刪除了,發現也沒有什么很好的標記可以說明一個文件正在被使用中。最后,發現文件鎖(FileLock)可以給文件一個鎖,另一個程序在使用的時候判斷文件是否有文件鎖,就可以判斷出文件是否正在使用。但是這種方法,在卸載SD卡的時候,好像接收不到SD卡卸載廣播,不知道是什么原因,最后還是使用的Sharepreference,跨文件訪問。不過文件鎖也是一個比較實用的知識點,了解一下。
JDK 1.4引入了文件加鎖機制,允許我們同步訪問一個共享文件,不過,競爭同一文件的兩個線程有可能在不同的java虛擬機上,或者一個是java線程,另一個是操作系統中其他的某個線程,但文件鎖對其他線程或其他操作系統進程都是可見的,因為java的文件加鎖直接映射到了本地操作系統的加鎖機制。
注,這里講的鎖是指鎖定其他應用程序,而不是鎖定同一虛擬機里訪問的同一文件的其他線程 。如果在同一虛擬機兩次鎖定同一文件或某文件里的同一區域,tryLock與lock則會拋出OverlappingFileLockException異常。
要想獲取整個文件的鎖,可以用FileChannel的tryLock( )或lock( )方法。(SocketChannel,DatagramChannel,以及 ServerSocketChannel是不需要鎖的,因為它們是從單進程實體繼承而來;一般來說,你是不會讓兩個進程去共享一個網絡socket的。tryLock( ) 是非阻塞的,它會試着去獲取這個鎖,但是如果得不到(其它進程已經以獨占方式得到這個鎖了),那它就直接返回;而lock( )是阻塞的,如果得不到鎖,它會在一直處於阻塞狀態,除非它得到了鎖,或者你打斷了調用它的線程,或者關閉了它要lock()的channel,否則它是不會返回的。最后用FileLock.release( )釋放鎖。
還可以像這樣鎖住文件的某一部分
tryLock(long position, long size, boolean shared)
或者
lock(long position, long size, boolean shared)
這個方法能鎖住文件的某個區域(size – position)。其中第三個參數表示是否是共享鎖。
雖然在修改文件的過程中,無參數的lock( )和tryLock( )方法的鎖定范圍會隨文件大小的變化,帶參數的方法卻不行。如果你鎖住了position到position+size這段范圍,而文件的長度又增加了,那么position+size后面是不加鎖的。而無參數的lock方法則會鎖定整個文件,不管它變不變長。
鎖是獨占的還是共享的,這要由操作系統來決定。如果操作系統不支持共享鎖,而程序又申請了一個共享鎖,那么它會返回一個獨占鎖。你可以用FileLock.isShared( )來查詢鎖的類型(共享還是獨占)。
在寫文件時才能鎖定,如果對一個只讀文件通道進行鎖定操作時,會拋NonWritableChannelException異常,即new FileInputStream(“data2.txt”).getChannel().tryLock();時就會拋異常。
另外鎖定寫文件通道new FileOutputStream(“data2.txt”).getChannel().tryLock();時,它會清掉原文件中的內容,所以當文件中有內容時最好使用 new FileOutputStream(“data2.txt”,true).getChannel().tryLock(); 以追加方式打開寫文件通道。或者使用RandomAccessFile類來創建文件通道然后鎖定 new RandomAccessFile(“data2.txt”,”rw”).getChannel().tryLock(); ,這樣它不會破壞鎖定的文件的內容。
最后在使用tryLock()獲取鎖時, 有可能獲取不到,這時就會為null,我們需能對此做相應處理。以下是簡單的銷實例:
- import java.io.FileOutputStream;
- import java.nio.channels.FileLock;
- public class FileLocking {
- public static void main(String[] args) throws Exception {
- FileOutputStream fos = new FileOutputStream("file.txt");
- //獲取文件鎖 FileLock 對象
- FileLock fl = fos.getChannel().tryLock();
- //tryLock是嘗試獲取鎖,有可能為空,所以要判斷
- if (fl != null) {
- System.out.println("Locked File");
- Thread.sleep(100);
- fl.release();//釋放鎖
- System.out.println("Released Lock");
- }
- fos.close();
- }
- }
鎖定映射文件中的部分內容
文件映射通常用於很大的文件,因此我們可能需要對文件操作的部分進行加鎖,以便其他進程可以修改文件中未被加鎖的部分。
-
import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; public class LockingMappedFiles { static final int LENGTH = 0x200000; // 2 Mb //static final int LENGTH = 100; static FileChannel fc; public static void main(String[] args) throws Exception { //使用可隨機訪問文件創建可讀寫文件通道 fc = new RandomAccessFile("test.txt", "rw").getChannel(); //內存映射可讀寫文件,並映射至整個文件 MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH); for (int i = 0; i < LENGTH; i++) {//寫滿2M內容 out.put((byte) 'x'); } //鎖定前1/3內容 new LockAndModify(out, 0, 0 + LENGTH / 3); //從文件中間開始鎖定1/4內容,注,要鎖定的內容一定不能有與 //已經鎖定的內容,否則拋OverlappingFileLockException new LockAndModify(out, LENGTH / 2, LENGTH / 2 + LENGTH / 4); } private static class LockAndModify extends Thread { private ByteBuffer buff; private int start, end; LockAndModify(ByteBuffer mbb, int start, int end) { this.start = start; this.end = end; //調整可最大讀寫位置 mbb.limit(end); //調整讀寫起始位置 mbb.position(start); //創建新的子緩沖區,但與原緩沖是共享同一片數據, //只是緩沖區位置、界限和標記值是相互獨立的 buff = mbb.slice(); start(); } public void run() { try { // 獲取獨占鎖,如果要鎖定的部分被其他應用程序鎖定,則會阻塞,至到獲取鎖為止 FileLock fl = fc.lock(start, end, false); System.out.println("Locked: " + start + " to " + end); System.out.println(buff.position() + " " + buff.limit()); // 進行修改操作,前當前位置類 while (buff.position() < buff.limit() - 1) { buff.put((byte) (buff.get() + 1)); } //JVM退出,或者channel關閉的時候會自動釋放這些鎖,但是你也可以用FileLock //的release( )方法,明確地釋放鎖,就像這里釋放鎖一樣 fl.release(); System.out.println("Released: " + start + " to " + end); } catch (IOException e) { throw new RuntimeException(e); } } } }