Java文件锁以及并发读写中的应用

引言

​ 在项目中,如果遇到需要并发读写文件的问题,那么对文件上锁分开访问是十分有必要的。因此这篇博文主要介绍文件锁的相关知识。有误之处,希望指出。

什么是文件锁

​ 文件锁就如同编程概念中其他锁的意义一样。通过对文件上锁,只有拥有锁的进程才能拥有对应锁权限的操作权限。而没有锁的进程只能挂起或者处理其他的事务直到拥有锁。从而在并发的场景下,我们才能对文件的读写进行控制。

分类

  • 共享锁:获取到共享锁,会阻止获取独占锁,但不会阻止获取共享锁。获取到共享锁可以同时读,但只能有一个在写。
  • 独占锁:获取到独占锁后,会阻止其他进程获得任何锁。只能一个读或者一个写。

注意项和异常

  • 首先务必注意,获取的文件锁是进程之间的锁。因此获得锁时,进程下的线程都会获得这个锁。由于在一个进程内,锁没有被释放的情况下,再次使用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();
                }
            }
        }

    }
}

以下是一些使用文件锁控制文件并发读写的测试代码,如果感兴趣的话,可以继续往下看:

  1. 获得文件锁后写文件代码

    
    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();
                }
            }
        }
    }
    }
  2. 获得文件锁后读文件代码

    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();
        }
    }
    }
  3. 测试客户端代码

    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
  4. 可以看到,四个并发的线程依次访问原文件,依次获得锁的权限。不因为并发问题导致文件读写出错。

再增加两组不获取锁直接进行读写的线程,可以看到共享锁和独占锁的区别:

  1. 不加锁的读线程
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!!");
        }
    }

}
  1. 不加锁的写线程
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先获得锁,此时另外两个读线程34可以并发进行读操作,而线程1因为需要持有锁才能操作,因此阻塞中。而写线程34因为共享锁不支持同时读写,因此写入失败。
  • 独占锁测试
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再获得锁,完成写入。其他不加锁的线程,无论是写还是读统统处理失败。
    原文作者:java锁
    原文地址: https://blog.csdn.net/paditang/article/details/73087628
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞