引言
在項目中,如果遇到需要併發讀寫文件的問題,那麼對文件上鎖分開訪問是十分有必要的。因此這篇博文主要介紹文件鎖的相關知識。有誤之處,希望指出。
什麼是文件鎖
文件鎖就如同編程概念中其他鎖的意義一樣。通過對文件上鎖,只有擁有鎖的進程才能擁有對應鎖權限的操作權限。而沒有鎖的進程只能掛起或者處理其他的事務直到擁有鎖。從而在併發的場景下,我們才能對文件的讀寫進行控制。
分類
- 共享鎖:獲取到共享鎖,會阻止獲取獨佔鎖,但不會阻止獲取共享鎖。獲取到共享鎖可以同時讀,但只能有一個在寫。
- 獨佔鎖:獲取到獨佔鎖後,會阻止其他進程獲得任何鎖。只能一個讀或者一個寫。
注意項和異常
首先務必注意,獲取的文件鎖是進程之間的鎖。因此獲得鎖時,進程下的線程都會獲得這個鎖。由於在一個進程內,鎖沒有被釋放的情況下,再次使用lock()獲取鎖的話,會拋出java.nio.channels.OverlappingFileLockException異常。所以多線程處理文件時,可以使用lock()方法捕獲異常,使得線程之間依次操作文件。
雖然常規都是使用new RandomAccessFile(file,”rw”).getChannel().lock();獲得鎖。
但是如果使用new FileInputStream(file).getChannel().lock();獲得鎖時。會拋出NonWritableChannelException異常。
如果使用new FileOutputStream(file).getChannel().lock()時,會先將原文件的內容清空,因此最好使用
new FileOutputStream(file,true).getChannel().lock()。
lock()和tryLock()的區別:
lock()方法當無法獲得鎖時會阻塞。
tryLock()方法當無法獲得鎖時會獲得null值。
代碼實現
public class FileLockDemo {
public static void main(String []args){
File file = new File("d:/test/test.txt");
RandomAccessFile raf = null;
FileChannel fc = null;
FileLock fl = null;
try{
raf = new RandomAccessFile(file, "rw");
fc = raf.getChannel();
//此處主要是針對多線程獲取文件鎖時輪詢鎖的狀態。如果只是單純獲得鎖的話,直接fl = fc.tryLock();即可
while(true){
try{
//無參獨佔鎖
fl = fc.tryLock();
//採用共享鎖
//fl = fc.tryLock(0,Long.MAX_VALUE,true);
if(fl!=null){
System.out.println("get the lock");
break;
}
}catch(Exception e){
//如果是同一進程的多線程,重複請求tryLock()會拋出OverlappingFileLockException異常
System.out.println("current thread is block");
}
}
//獲得文件鎖權限後,進行相應的操作
fl.release();
fc.close();
raf.close();
}catch(Exception e){
e.printStackTrace();
}finally{
if(fl!=null&&fl.isValid()){
try {
fl.release();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
以下是一些使用文件鎖控制文件併發讀寫的測試代碼,如果感興趣的話,可以繼續往下看:
獲得文件鎖後寫文件代碼
public class WriteThread extends Thread{ private String sourceFile; private String targetFile; private String threadName; public WriteThread(String sourceFile,String targetFile,String ThreadName){ this.sourceFile = sourceFile; this.targetFile = targetFile; this.threadName = ThreadName; } @Override public void run(){ RandomAccessFile raf = null; FileChannel fc = null; FileLock fl = null; FileInputStream in = null; try { raf = new RandomAccessFile(targetFile, "rw"); fc = raf.getChannel(); while(true){ try { fl = fc.tryLock(); System.out.println(fl.isShared()); System.out.println(this.threadName+" : get the lock"); try { sleep(1000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } break; } catch (Exception e) { System.out.println(this.threadName+" is block"); try { sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } } } in = new FileInputStream(sourceFile); byte[] b = new byte[1024]; int len = 0; ByteBuffer bb = ByteBuffer.allocate(1024); while((len=in.read(b))!=-1){ bb.clear(); bb.put(b, 0, len); bb.flip(); fc.write(bb); } System.out.println(this.threadName+" : write success"); fl.release(); System.out.println(this.threadName+" : release lock"); raf.close(); fc.close(); in.close(); } catch (Exception e) { // TODO Auto-generated catch block //e.printStackTrace(); System.out.println(this.threadName+" : write failed"); }finally{ if(fl!=null&&fl.isValid()){ try { fl.release(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
獲得文件鎖後讀文件代碼
public class ReadThread extends Thread{ private String sourceFile; private String threadName; public ReadThread(String sourceFile,String threadName){ this.sourceFile = sourceFile; this.threadName = threadName; } @Override public void run(){ RandomAccessFile raf = null; FileChannel fc = null; FileLock fl = null; try { raf = new RandomAccessFile(sourceFile, "rw"); fc = raf.getChannel(); while(true){ try { fl = fc.tryLock(0,Long.MAX_VALUE,true); System.out.println(fl.isShared()); System.out.println(this.threadName+" : get the lock"); try { sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } break; } catch (Exception e) { // TODO Auto-generated catch block //e.printStackTrace(); System.out.println(this.threadName+" is block"); try { sleep(1000); } catch (InterruptedException e1) { e1.printStackTrace(); } } } StringBuffer sb = new StringBuffer(); sb.append("Read from "+this.threadName+":"); ByteBuffer bb = ByteBuffer.allocate(1024); while((fc.read(bb))!=-1){ //sb.append(new String(bb.array())); //System.out.println(new String(bb.array())); bb.clear(); } System.out.println(this.threadName+" : read success!"); fl.release(); System.out.println(this.threadName+" : release lock"); raf.close(); fc.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
測試客戶端代碼
public class Client { public static void main(String []args){ new WriteThread("d:/test/test_1.txt", "d:/test/test.txt", "write_thread-1").start(); new WriteThread("d:/test/logs", "d:/test/test.txt", "write_thread-2").start(); new ReadThread("d:/test/test.txt", "read_thread-1").start(); new ReadThread("d:/test/test.txt", "read_thread-2").start(); } }
輸出結果: true read_thread-1 : get the lock read_thread-2 is block write_thread-1 is block write_thread-2 is block read_thread-2 is block write_thread-1 is block write_thread-2 is block read_thread-1 : read success! read_thread-1 : release lock true write_thread-2 is block read_thread-2 : get the lock write_thread-1 is block write_thread-2 is block write_thread-1 is block read_thread-2 : read success! read_thread-2 : release lock false write_thread-1 is block write_thread-2 : get the lock write_thread-1 is block write_thread-2 : write success write_thread-2 : release lock false write_thread-1 : get the lock write_thread-1 : write success write_thread-1 : release lock
可以看到,四個併發的線程依次訪問原文件,依次獲得鎖的權限。不因爲併發問題導致文件讀寫出錯。
再增加兩組不獲取鎖直接進行讀寫的線程,可以看到共享鎖和獨佔鎖的區別:
- 不加鎖的讀線程
public class NormalReadThread extends Thread{
private String sourceFile;
private String threadName;
public NormalReadThread(String sourceFile,String threadName){
this.sourceFile = sourceFile;
this.threadName = threadName;
}
@Override
public void run(){
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(sourceFile, "rw");
FileChannel fc = raf.getChannel();
StringBuffer sb = new StringBuffer();
sb.append("Read from "+this.threadName+":");
ByteBuffer bb = ByteBuffer.allocate(1024);
while((fc.read(bb))!=-1){
//sb.append(new String(bb.array()));
bb.clear();
}
System.out.println(this.threadName+" : read success!");
raf.close();
fc.close();
} catch(Exception e){
e.printStackTrace();
System.out.println(this.threadName+" : read failed!!");
}
}
}
- 不加鎖的寫線程
public class NormalWriteThread extends Thread{
private String sourceFile;
private String targetFile;
private String threadName;
public NormalWriteThread(String sourceFile,String targetFile,String ThreadName){
this.sourceFile = sourceFile;
this.targetFile = targetFile;
this.threadName = ThreadName;
}
@Override
public void run(){
RandomAccessFile raf = null;
FileInputStream in = null;
try {
raf = new RandomAccessFile(targetFile, "rw");
FileChannel fc = raf.getChannel();
in = new FileInputStream(sourceFile);
ByteBuffer bb = ByteBuffer.allocate(1024);
byte[] b = new byte[1024];
int len = 0;
while((len=in.read(b))!=-1){
bb.clear();
bb.put(b, 0, len);
fc.write(bb);
}
System.out.println(this.threadName+" : write success");
fc.close();
raf.close();
in.close();
} catch(Exception e){
//e.printStackTrace();
System.out.println(this.threadName+" : write failed!!");
}
}
}
測試代碼:
- 共享鎖測試
public class Client {
public static void main(String []args){
/*new WriteThread("d:/test/test_1.txt", "d:/test/test.txt", "write_thread-1").start();
new WriteThread("d:/test/logs", "d:/test/test.txt", "write_thread-2").start();*/
new ReadThread("d:/test/test.txt", "read_thread-1").start();
new ReadThread("d:/test/test.txt", "read_thread-2").start();
new NormalWriteThread("d:/test/test_copy1.txt", "d:/test/test.txt", "write_thread-3").start();
new NormalWriteThread("d:/test/test_copy2.txt","d:/test/test.txt", "write_thread-4").start();
new NormalReadThread("d:/test/test.txt", "read_thread-3").start();
new NormalReadThread("d:/test/test.txt", "read_thread-4").start();
}
}
測試結果:
true
read_thread-2 : get the lock
read_thread-4 : read success!
read_thread-3 : read success!
read_thread-1 is block
write_thread-4 : write failed!!
write_thread-3 : write failed!!
read_thread-1 is block
read_thread-2 : read success!
read_thread-2 : release lock
true
read_thread-1 : get the lock
read_thread-1 : read success!
read_thread-1 : release lock
可以看出,讀線程2先獲得鎖,此時另外兩個讀線程3、4可以併發進行讀操作,而線程1因爲需要持有鎖才能操作,因此阻塞中。而寫線程3、4因爲共享鎖不支持同時讀寫,因此寫入失敗。
- 獨佔鎖測試
public class Client {
public static void main(String []args){
new WriteThread("d:/test/test_1.txt", "d:/test/test.txt", "write_thread-1").start();
new WriteThread("d:/test/logs", "d:/test/test.txt", "write_thread-2").start();
/*new ReadThread("d:/test/test.txt", "read_thread-1").start();
new ReadThread("d:/test/test.txt", "read_thread-2").start();*/
new NormalWriteThread("d:/test/test_copy1.txt", "d:/test/test.txt", "write_thread-3").start();
new NormalWriteThread("d:/test/test_copy2.txt","d:/test/test.txt", "write_thread-4").start();
new NormalReadThread("d:/test/test.txt", "read_thread-3").start();
new NormalReadThread("d:/test/test.txt", "read_thread-4").start();
}
}
測試結果:
false
write_thread-2 : get the lock
read_thread-4 : read failed!!
read_thread-3 : read failed!!
write_thread-1 is block
write_thread-4 : write failed!!
write_thread-3 : write failed!!
write_thread-1 is block
write_thread-2 : write success
write_thread-2 : release lock
false
write_thread-1 : get the lock
write_thread-1 : write success
write_thread-1 : release lock
從結果上看,寫線程2先獲得鎖,完成寫入後,寫線程1再獲得鎖,完成寫入。其他不加鎖的線程,無論是寫還是讀統統處理失敗。