引言
在项目中,如果遇到需要并发读写文件的问题,那么对文件上锁分开访问是十分有必要的。因此这篇博文主要介绍文件锁的相关知识。有误之处,希望指出。
什么是文件锁
文件锁就如同编程概念中其他锁的意义一样。通过对文件上锁,只有拥有锁的进程才能拥有对应锁权限的操作权限。而没有锁的进程只能挂起或者处理其他的事务直到拥有锁。从而在并发的场景下,我们才能对文件的读写进行控制。
分类
- 共享锁:获取到共享锁,会阻止获取独占锁,但不会阻止获取共享锁。获取到共享锁可以同时读,但只能有一个在写。
- 独占锁:获取到独占锁后,会阻止其他进程获得任何锁。只能一个读或者一个写。
注意项和异常
首先务必注意,获取的文件锁是进程之间的锁。因此获得锁时,进程下的线程都会获得这个锁。由于在一个进程内,锁没有被释放的情况下,再次使用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再获得锁,完成写入。其他不加锁的线程,无论是写还是读统统处理失败。