È un blocco di lettura su un ReentrantReadWriteLock sufficiente per la lettura contemporanea di un RandomAccessFile

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

Domanda

sto scrivendo qualcosa per gestire le richieste simultanee di lettura / scrittura in un file di database.

ReentrantReadWriteLock si presenta come una buona partita. Se tutti i thread accedono un oggetto condiviso RandomAccessFile , ho bisogno di preoccuparsi per il puntatore del file con i lettori concorrenti? Considerate questo esempio:

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

}

Nel metodo GetRecord (), è il seguente interleaving possibile con più lettori simultanei?

  

Thread 1 -> GetRecord (0)
  Discussione 2 -> GetRecord (1)
  Discussione 1 -> acquisisce condiviso blocco
  Discussione 2 -> acquisisce condiviso blocco
  Discussione 1 -> cerca di registrare 0
  Discussione 2 -> cerca di registrare 1
  Discussione 1 -> si legge record puntatore a file (1)
  Discussione 2 -> si legge record puntatore a file (1)

Se ci sono infatti i potenziali problemi di concorrenza utilizzando ReentrantReadWriteLock e RandomAccessFile, quale sarebbe un'alternativa essere?

È stato utile?

Soluzione

Sì, questo codice non è sincronizzato correttamente, proprio come si contorno. Un blocco di lettura-scrittura non è utile se il blocco di scrittura non è mai acquisita; è come se non v'è alcun blocco.

Utilizzare un blocco synchronized tradizionale per rendere la ricerca e lettura appaiono atomica ad altri thread, o di creare un pool di istanze RandomAccessFile che sono presi in prestito per l'uso esclusivo di un singolo thread e poi restituiti. (O semplicemente dedicare un canale per ogni thread, se non si dispone di troppi thread.)

Altri suggerimenti

Questo è un esempio di programma che bloccare il file e file di sbloccare.

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

Si consiglia di considerare l'utilizzo di blocchi di file di sistema, invece di gestire il proprio blocco.

chiamata getChannel().lock() sul RandomAccessFile per bloccare il file tramite la classe FileChannel. Questo impedisce l'accesso in scrittura, anche dai processi al di fuori del vostro controllo.

Piuttosto operare sull'oggetto blocco singolo invece del metodo, ReentrantReadWriteLock può supportare fino a un massimo di 65535 blocchi di scrittura ricorsive e 65535 leggere serrature.

Assegnare una lettura e scrittura di blocco

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

Quindi lavorare su di loro ...

Inoltre: non sono catering per un'eccezione e fallimento per sbloccare successivamente alla chiusura. Chiamare la serratura come si entra il metodo (come un armadietto mutex) e poi fare il vostro lavoro in un blocco try / catch con lo sblocco nella sezione, infine, ad esempio:

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

OK, 8,5 anni è un tempo lungo, ma spero che non è necro ...

Il mio problema era che avevamo bisogno di accedere a flussi di leggere e scrivere come atomica possibile. Una parte importante è che il nostro codice avrebbe dovuto funzionare su più macchine che accedono lo stesso file. Tuttavia, tutti gli esempi su Internet fermato a spiegare come bloccare un RandomAccessFile e non andare più in profondità. Quindi il mio punto di partenza è stato di Sam risposta .

Ora, a distanza ha senso avere un certo ordine:

  • bloccare il file
  • Apri i flussi
  • fare tutto ciò con i flussi
  • chiudere i flussi
  • rilasciare il blocco

Tuttavia, per consentire il rilascio del blocco in Java i flussi non devono essere chiuse! A causa di ciò l'intero meccanismo diventa un po 'strano (e sbagliato?).

Al fine di rendere il lavoro di auto-chiusura si deve ricordare che JVM chiude le entità in ordine inverso del try-segmento. Ciò significa che un flusso simile a questo:

  • Apri i flussi
  • bloccare il file
  • fare tutto ciò con i flussi
  • rilasciare il blocco
  • chiudere i flussi

I test hanno dimostrato che questo non funziona. Pertanto, chiusura automatica a metà strada e fare il resto in buona ol' Java 1 moda:

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

Si noti che i metodi che utilizzano questo devono ancora essere synchronized. In caso contrario, le esecuzioni parallele possono gettare un OverlappingFileLockException quando si chiama lock().

Si prega di condividere le esperienze nel caso si abbia qualsiasi ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top