Est un verrou de lecture sur une ReentrantReadWriteLock suffisante pour la lecture simultanée d'un RandomAccessFile

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

Question

J'écris quelque chose pour traiter les demandes de lecture / écriture simultanées dans un fichier de base de données.

ReentrantReadWriteLock ressemble à un bon match. Si tous les threads d'accéder à un partage RandomAccessFile objet , dois-je à vous soucier du pointeur de fichier avec les lecteurs simultanés? Considérez cet exemple:

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

}

Dans la méthode getRecord (), est le suivant entrelacer possible avec plusieurs lecteurs simultanés?

  

Discussion 1 -> getRecord (0)
  Discussion 2 -> getRecord (1)
  Discussion 1 -> verrou partagé
acquiert   Discussion 2 -> acquiert verrou partagé
  Discussion 1 -> cherche à enregistrer 0
  Discussion 2 -> cherche à enregistrer 1
  Discussion 1 -> lit enregistrement au pointeur de fichier (1)
  Discussion 2 -> lit enregistrement au pointeur de fichier (1)

S'il y a effectivement des problèmes de concurrence potentiels à l'aide ReentrantReadWriteLock et RandomAccessFile, ce qui serait une alternative?

Était-ce utile?

La solution

Oui, ce code n'est pas synchronisé correctement, comme vous le contour. Un verrou de lecture-écriture est pas utile si le verrou d'écriture est jamais acquise; il est comme s'il n'y a pas de verrou.

Utilisez un bloc traditionnel synchronized pour faire la recherche et de lecture apparaissent atomique à d'autres fils de discussion, ou créer un pool d'instances de RandomAccessFile qui sont empruntés à l'usage exclusif d'un seul fil, puis retourné. (Ou tout simplement dédier un canal à chaque fil, si vous n'avez pas trop de threads).

Autres conseils

Ceci est un exemple de programme ce fichier verrouiller et déverrouiller le fichier.

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

Vous pouvez envisager d'utiliser les verrous du système de fichiers au lieu de gérer votre propre verrouillage.

Appel getChannel().lock() sur votre RandomAccessFile pour verrouiller le fichier via la classe FileChannel. Cela empêche l'accès en écriture, même des processus hors de votre contrôle.

plutôt opérer sur l'objet de verrouillage unique au lieu de la méthode, ReentrantReadWriteLock peut prendre en charge jusqu'à un maximum de 65535 verrous d'écriture récursifs et 65535 lire les verrous.

Affectation d'une lecture et d'écriture de verrouillage

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

Ensuite, travailler sur eux ...

En outre: vous n'êtes pas une exception pour la restauration et l'absence de déverrouillage après verrouillage. Appelez le verrou que vous entrez dans la méthode (comme un casier mutex) puis faites votre travail dans un bloc try / catch avec le déverrouillage dans la section enfin, par exemple:

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

OK, 8,5 ans est une longue période, mais je l'espère, ce n'est pas nécro ...

Mon problème était que nous avions besoin d'accéder à des flux pour lire et écrire en tant atomique que possible. Une part importante est que notre code était censé fonctionner sur plusieurs machines accédant au même fichier. Cependant, tous les exemples sur Internet arrêtés à expliquer comment verrouiller un RandomAccessFile et ne va pas plus profond. Donc, mon point de départ était réponse de Sam .

, de loin, il est logique d'avoir un certain ordre:

  • verrouiller le fichier
  • ouvrir les flux
  • faire tout avec les courants
  • fermer les flux
  • libérer le verrou

Cependant, pour permettre la libération de la serrure en Java les flux ne doivent pas être fermés! En raison de ce que le mécanisme entier devient un peu bizarre (et le mal?).

Afin de rendre le travail de l'auto-fermeture il faut se rappeler que JVM ferme les entités dans l'ordre inverse de l'essai-segment. Cela signifie qu'un flux ressemble à ceci:

  • ouvrir les flux
  • verrouiller le fichier
  • faire tout avec les courants
  • libérer le verrou
  • fermer les flux

Les tests ont montré que cela ne fonctionne pas. Par conséquent, chemin de fermeture automatique de la moitié et faire le reste en bon vieux mode 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();
}

Notez que les méthodes utilisant ce doit encore être synchronized. Sinon, les exécutions parallèles peuvent jeter un OverlappingFileLockException lors de l'appel lock().

S'il vous plaît de partager leurs expériences dans le cas où vous avez une ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top