É um bloqueio de leitura em um reentrantreadwritelock suficiente para a leitura simultânea de um RandomAccessFile

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

Pergunta

Estou escrevendo algo para lidar com solicitações simultâneas de leitura/gravação em um arquivo de banco de dados.

ReentrantreadWritelock Parece uma boa combinação. Se todos os threads acessarem um compartilhado RandomAccessFile Objeto, preciso me preocupar com o ponteiro de arquivo com leitores simultâneos? Considere este exemplo:

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;
    }

}

No método getRecord (), é a seguinte intercalação possível com vários leitores simultâneos?

Tópico 1 -> getRecord (0)
Tópico 2 -> GetRecord (1)
Tópico 1 -> adquire o bloqueio compartilhado
Tópico 2 -> adquire o bloqueio compartilhado
Tópico 1 -> procura gravar 0
Tópico 2 -> procura gravar 1
Tópico 1 -> Reads Registro no Ponteiro de Arquivo (1)
Tópico 2 -> Reads Registro no Ponteiro de Arquivo (1)

Se houver de fato problemas de concorrência em potencial usando o reentrantreadwritelock e o RandomAccessFile, qual seria uma alternativa?

Foi útil?

Solução

Sim, este código não é sincronizado corretamente, assim como você descreve. Um bloqueio de leitura e gravação não é útil se o bloqueio de gravação nunca for adquirido; É como se não houvesse bloqueio.

Use um tradicional synchronized bloco para fazer a busca e a leitura pareça atômica para outros threads ou criar um pool de RandomAccessFile Instâncias emprestadas para o uso exclusivo de um único thread e depois retornadas. (Ou simplesmente dedique um canal a cada tópico, se você não tiver muitos threads.)

Outras dicas

Este é um programa de amostra que bloqueia o arquivo e desbloqueia o arquivo.

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) { } 

Você pode considerar o uso de bloqueios do sistema de arquivos em vez de gerenciar seu próprio bloqueio.

Ligar getChannel().lock() no seu RandomAccessFile para bloquear o arquivo através do FileChannel classe. Isso impede o acesso de gravação, mesmo de processos fora do seu controle.

Em vez disso, opere no objeto de bloqueio único, e não no método, o ReentrantReadWriteLock pode suportar até um máximo de 65535 bloqueios de gravação recursiva e 65535 de leitura.

Atribua um bloqueio de leitura e gravação

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

Então trabalhe neles ...

Além disso: você não está atendendo a uma exceção e falha em desbloquear subsequente ao bloqueio. Ligue para o bloqueio ao entrar no método (como um armário mutex) e faça seu trabalho em um bloco de tentativa/captura com o desbloqueio na seção Finalmente, por exemplo:

public String[] allKeys() {
  r.lock();
  try { return m.keySet().toArray(); }
  finally { r.unlock(); }
}

Ok, 8,5 anos é muito tempo, mas espero que não seja necro ...

Meu problema era que precisávamos acessar fluxos para ler e escrever o mais atômico possível. Uma parte importante era que nosso código deveria ser executado em várias máquinas acessando o mesmo arquivo. No entanto, todos os exemplos na internet pararam para explicar como bloquear um RandomAccessFile e não foi mais profundo. Então meu ponto de partida foi Resposta de Sam.

Agora, à distância, faz sentido ter uma certa ordem:

  • bloquear o arquivo
  • Abra os fluxos
  • Faça o que quer com os riachos
  • Feche os fluxos
  • Libere o bloqueio

No entanto, para permitir a liberação da fechadura em Java, os riachos não devem ser fechados! Por causa disso, todo o mecanismo se torna um pouco estranho (e errado?).

Para fazer o trabalho de fechamento automático, é preciso lembrar que a JVM fecha as entidades na ordem inversa do segmento de Try. Isso significa que um fluxo se parece com o seguinte:

  • Abra os fluxos
  • bloquear o arquivo
  • Faça o que quer com os riachos
  • Libere o bloqueio
  • Feche os fluxos

Os testes mostraram que isso não funciona. Portanto, fechar automaticamente a metade do caminho e fazer o resto da maneira boa e velha 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();
}

Observe que os métodos que usam isso ainda devem ser synchronized. Caso contrário, as execuções paralelas podem jogar um OverlappingFileLockException Ao ligar lock().

Por favor, compartilhe experiências, caso você tenha algum ...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top