É um bloqueio de leitura em um reentrantreadwritelock suficiente para a leitura simultânea de um RandomAccessFile
-
22-09-2019 - |
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?
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 ...