Достаточно ли блокировки чтения в 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) { }
Возможно, вы захотите использовать блокировки файловой системы вместо управления собственной блокировкой.
Вызов getChannel().lock()
в вашем RandomAccessFile, чтобы заблокировать файл через FileChannel
сорт.Это предотвращает доступ для записи даже со стороны процессов, находящихся вне вашего контроля.
Вместо того, чтобы работать с одним объектом блокировки, а не с методом, ReentrantReadWriteLock может поддерживать до 65 535 рекурсивных блокировок записи и 65 535 блокировок чтения.
Назначить блокировку чтения и записи
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
Тогда работайте над ними...
Также:вы не учитываете исключение и невозможность разблокировки после блокировки.Вызовите блокировку при входе в метод (например, блокировку мьютекса), затем выполните свою работу в блоке try/catch с разблокировкой в разделе Final, например:
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()
.
Поделитесь, пожалуйста, опытом, если у кого-то есть...