Достаточно ли блокировки чтения в ReentrantReadWriteLock для одновременного чтения RandomAccessFile?

StackOverflow https://stackoverflow.com/questions/1587218

Вопрос

Я пишу что-то для обработки одновременных запросов на чтение/запись в файл базы данных.

Повторный входЧтениеЗаписьБлокировка выглядит как хороший матч.Если все потоки имеют доступ к общему СлучайныйДоступФайл объект, нужно ли мне беспокоиться об указателе файла при одновременном чтении?Рассмотрим этот пример:

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().

Поделитесь, пожалуйста, опытом, если у кого-то есть...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top