我正在写一些东西来处理对数据库文件的并发读/写请求。
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确实存在潜在的并发问题,那么替代方案会是什么?
答案 0 :(得分:4)
这是一个锁定文件和解锁文件的示例程序。
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) { }
答案 1 :(得分:2)
是的,正如您所概述的那样,此代码未正确同步。如果从未获取写锁,则读写锁无用;就像没有锁一样。
使用传统的synchronized
块使搜索和读取对其他线程显示为原子,或者创建一个RandomAccessFile
实例池,这些实例借用于单个线程的独占使用,然后返回。 (或者,如果没有太多线程,只需将通道专用于每个线程。)
答案 2 :(得分:2)
您可能需要考虑使用文件系统锁而不是管理自己的锁。
在RandomAccessFile上调用getChannel().lock()
以通过FileChannel
类锁定文件。这可以防止写入访问,即使是来自控制之外的进程也是如此。
答案 3 :(得分:1)
而不是对单个锁对象而不是方法进行操作,ReentrantReadWriteLock最多可以支持65535个递归写锁和65535个读锁。
分配读写锁定
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
然后继续努力......
另外:您不会遇到异常,也无法在锁定后解锁。在输入方法时调用锁(如互斥锁),然后使用finally部分中的unlock在try / catch块中完成工作,例如:
public String[] allKeys() {
r.lock();
try { return m.keySet().toArray(); }
finally { r.unlock(); }
}
答案 4 :(得分:0)
好的,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
。否则,并行执行可能会在调用OverlappingFileLockException
时抛出lock()
。
如果您有任何问题,请分享经验...