【问题标题】:Is a read lock on a ReentrantReadWriteLock sufficient for concurrent reading of a RandomAccessFileReentrantReadWriteLock 上的读锁是否足以并发读取 RandomAccessFile
【发布时间】:2009-10-19 06:26:13
【问题描述】:

我正在编写一些东西来处理对数据库文件的并发读/写请求。

ReentrantReadWriteLock 看起来不错。如果所有线程都访问共享的RandomAccessFile 对象,我是否需要担心并发读取器的文件指针?考虑这个例子:

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Database {

    private static final int RECORD_SIZE = 50;
    private static Database instance = null;

    private ReentrantReadWriteLock lock;
    private RandomAccessFile database;

    private Database() {
        lock = new ReentrantReadWriteLock();

        try {
            database = new RandomAccessFile("foo.db", "rwd");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    };

    public static synchronized Database getInstance() {
        if(instance == null) {
            instance = new Database();
        }
        return instance;
    }

    public byte[] getRecord(int n) {
        byte[] data = new byte[RECORD_SIZE];
        try {
            // Begin critical section
            lock.readLock().lock();
            database.seek(RECORD_SIZE*n);
            database.readFully(data);
            lock.readLock().unlock();
            // End critical section
        } catch (IOException e) {
            e.printStackTrace();
        }
        return data;
    }

}

在 getRecord() 方法中,多个并发读取器是否可以进行以下交错?

线程 1 -> getRecord(0)
线程 2 -> getRecord(1)
线程 1 -> 获取共享锁
线程 2 -> 获取共享锁
线程 1 -> 寻求记录 0
线程 2 -> 寻求记录 1
线程 1 -> 在文件指针处读取记录 (1)
线程 2 -> 在文件指针 (1) 处读取记录

如果使用 ReentrantReadWriteLock 和 RandomAccessFile 确实存在潜在的并发问题,那么替代方案是什么?

【问题讨论】:

    标签: java file-io locking readwritelock reentrantreadwritelock


    【解决方案1】:

    这是一个锁定文件和解锁文件的示例程序。

    try { // Get a file channel for the file 
    
        File file = new File("filename");
    
        FileChannel channel = new RandomAccessFile(file, "rw").getChannel(); // Use the file channel to create a lock on the file.
    
        // This method blocks until it can retrieve the lock. 
    
        FileLock lock = channel.lock(); // Try acquiring the lock without blocking. This method returns // null or throws an exception if the file is already locked. 
    
        try { 
    
            lock = channel.tryLock();
    
        } catch (OverlappingFileLockException e){}
    
    
        lock.release(); // Close the file 
    
        channel.close();
    } 
    
    catch (Exception e) { } 
    

    【讨论】:

    • 它似乎在 Linux 中不起作用。一般来说,我发现 java 的文件 Lock 没用。不过,同步会有所帮助。
    【解决方案2】:

    是的,正如您概述的那样,此代码未正确同步。如果从未获得写锁,则读写锁就没有用;好像没有锁一样。

    使用传统的synchronized 块使查找和读取对其他线程来说是原子的,或者创建一个RandomAccessFile 实例池,这些实例被借用以供单个线程独占使用然后返回。 (或者,如果您没有太多线程,则只需为每个线程专用一个通道。)

    【讨论】:

    • 读写锁对 RandomAccessFile 有用吗?似乎如果我使用传统的同步块,读取和写入将被视为相同,因此不会利用读取可以并行执行的事实。
    • 如果您实际上是在应用程序的某个地方进行一些写入,是的,读写锁可能很有用。例如,如果您有一个 RandomAccessFiles 池来支持并发读取,那么您需要一种方法在单个线程执行其写入时同时将所有这些文件排除在读取之外。那将是读写锁的一个很好的应用。
    【解决方案3】:

    您可能需要考虑使用文件系统锁而不是管理您自己的锁。

    在 RandomAccessFile 上调用 getChannel().lock() 以通过 FileChannel 类锁定文件。这可以防止写入访问,即使是您无法控制的进程。

    【讨论】:

      【解决方案4】:

      ReentrantReadWriteLock 最多支持65535个递归写锁和65535个读锁。

      分配读写锁

      private final Lock r = rwl.readLock();
      private final Lock w = rwl.writeLock();
      

      然后处理它们...

      另外:您不适合在锁定后出现异常和解锁失败。在进入方法时调用锁(如互斥锁),然后在 try/catch 块中完成工作,并在 finally 部分解锁,例如:

      public String[] allKeys() {
        r.lock();
        try { return m.keySet().toArray(); }
        finally { r.unlock(); }
      }
      

      【讨论】:

        【解决方案5】:

        好吧,8.5年很长,但我希望它不是死灵......

        我的问题是我们需要访问流以尽可能原子地读写。一个重要的部分是我们的代码应该在访问同一个文件的多台机器上运行。但是,互联网上的所有示例都停留在解释如何锁定RandomAccessFile 并且没有深入。所以我的出发点是Sam's answer

        现在,从远处看,有一定的顺序是有意义的:

        • 锁定文件
        • 打开流
        • 对流做任何事情
        • 关闭流
        • 释放锁

        但是,为了允许在 Java 中释放锁,不能关闭流!因此,整个机制变得有点奇怪(而且是错误的?)。

        为了使自动关闭工作,必须记住 JVM 以与 try-segment 相反的顺序关闭实体。这意味着流程看起来像这样:

        • 打开流
        • 锁定文件
        • 对流做任何事情
        • 释放锁
        • 关闭流

        测试表明这不起作用。因此,中途自动关闭并以良好的 Java 1 方式完成其余工作:

        try (RandomAccessFile raf = new RandomAccessFile(filename, "rwd");
            FileChannel channel = raf.getChannel()) {
          FileLock lock = channel.lock();
          FileInputStream in = new FileInputStream(raf.getFD());
          FileOutputStream out = new FileOutputStream(raf.getFD());
        
          // do all reading
          ...
        
          // that moved the pointer in the channel to somewhere in the file,
          // therefore reposition it to the beginning:
          channel.position(0);
          // as the new content might be shorter it's a requirement to do this, too:
          channel.truncate(0);
        
          // do all writing
          ...
        
          out.flush();
          lock.release();
          in.close();
          out.close();
        }
        

        请注意,使用它的方法仍然必须是synchronized。否则并行执行可能会在调用lock() 时抛出OverlappingFileLockException

        如果您有任何经验,请分享...

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-07
          • 2012-12-05
          • 1970-01-01
          • 1970-01-01
          • 2019-02-21
          相关资源
          最近更新 更多