ReentrantReadWriteLock 上的读锁是否足以并发读取 RandomAccessFile
-
22-09-2019 - |
题
我正在写一些东西来处理对数据库文件的并发读/写请求。
可重入读写锁 看起来很匹配。如果所有线程都访问共享 随机存取文件 对象,我需要担心并发读取器的文件指针吗?考虑这个例子:
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 确实存在潜在的并发问题,那么替代方案是什么?
解决方案
是的,这个代码不正确同步,就像你的轮廓。如果写锁从未获得过一个读写锁是没有用的;它是因为如果没有锁定。
使用传统synchronized
块,使搜寻和读取出现原子其他线程,或创建一个借来的独家使用一个线程,然后返回RandomAccessFile
实例池。 (或者干脆专门的通道给每个线程,如果你没有太多的线程。)
其他提示
这是一个示例的程序,锁文件和解锁文件。
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) { }
您可能要考虑使用的文件系统锁定,而不是管理自己的锁。
呼叫您的RandomAccessFile getChannel().lock()
锁定通过FileChannel
类的文件。这防止写入访问,甚至从你的控制之外的过程。
更确切地说,单个锁定对象,而不是在方法上操作,的ReentrantReadWriteLock可以支持高达最大值是65535个递归写锁和65535读锁。
分配的读和写锁
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
然后对他们的工作...
此外:你不迎合异常和故障解锁之后锁定。调用锁,当你进入方法(如互斥锁柜),然后做你的工作在一个try / catch块与解锁的最后部分,例如:
public String[] allKeys() {
r.lock();
try { return m.keySet().toArray(); }
finally { r.unlock(); }
}
好吧,8.5年是一段很长的时间,但我希望它不是坏死的……
我的问题是我们需要访问流以尽可能原子地读写。一个重要的部分是我们的代码应该在访问同一文件的多台机器上运行。然而,互联网上的所有例子都停留在解释如何锁定一个 RandomAccessFile
并且没有再深入。所以我的出发点是 萨姆的回答.
现在,从远处看,有一定的顺序是有意义的:
- 锁定文件
- 打开溪流
- 对流做任何事
- 关闭流
- 释放锁
然而,为了允许释放 Java 中的锁,流不能被关闭!因此,整个机制变得有点奇怪(而且是错误的?)。
为了使自动关闭工作,必须记住 JVM 以与 try 段相反的顺序关闭实体。这意味着流程如下所示:
- 打开溪流
- 锁定文件
- 对流做任何事
- 释放锁
- 关闭流
测试表明这不起作用。因此,中途自动关闭并以良好的 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()
.
请分享经验,如果您有任何...