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