代码之家  ›  专栏  ›  技术社区  ›  Ed Mazur

ReentrantreadWriteLock上的读取锁是否足以同时读取RandomAccess文件?

  •  4
  • Ed Mazur  · 技术社区  · 15 年前

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

    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->获取记录(1)
    线程1->获取共享锁
    线程2->获取共享锁
    线程1->查找记录0
    线程2->查找记录1
    线程1->在文件指针处读取记录(1)
    线程2->在文件指针处读取记录(1)

    如果使用ReentrantreadWriteLock和RandomAccessFile确实存在潜在的并发问题,那么还有什么选择呢?

    5 回复  |  直到 7 年前
        1
  •  2
  •   erickson    15 年前

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

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

        2
  •  4
  •   Adam Paynter    14 年前

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

    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) { } 
    
        3
  •  2
  •   Sam Barnum    15 年前

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

    呼叫 getChannel().lock() 在您的randomaccess文件上通过 FileChannel 班级。这会阻止写入访问,即使是来自您控制之外的进程。

        4
  •  1
  •   Adam Paynter    14 年前

    ReentranTreadWriteLock不支持对单个锁对象进行操作,而是支持最多65535个递归写锁和65535个读锁。

    分配读写锁

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

    然后再研究它们…

    另外:您不能处理异常情况,也不能在锁定之后解锁。在输入方法(如互斥锁)时调用锁,然后在try/catch块中使用finally部分中的unlock进行工作,例如:

    public String[] allKeys() {
      r.lock();
      try { return m.keySet().toArray(); }
      finally { r.unlock(); }
    }
    
        5
  •  0
  •   sjngm quinti    7 年前

    好吧,8.5年是很长的一段时间,但我希望这不是necro…

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

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

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

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

    为了使自动关闭工作正常进行,必须记住,JVM以与try段相反的顺序关闭实体。这意味着流程如下:

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

    测试表明这不起作用。因此,自动关闭一半,其余的以良好的OL“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 . 否则,并行执行可能会引发 OverlappingFileLockException 打电话时 lock() .

    请分享经验,以防你有任何…