Bloccare un file durante la scrittura sul disco
-
22-08-2019 - |
Domanda
Ho due fili indipendenti F1 e F2 (per la precisione, due istanze di java.util.concurrent.FutureTask) che eseguono in parallelo.
F1 fare un po 'di elaborazione, e quindi copiare il risultato in un file XML. Poi, si ripete questa procedura fino a quando non ha nulla a che fare (vengono creati molti file XML). F2 cerca nella directory di output di F1, e prendere un file, analizzarlo, ed eseguire alcune elaborazioni su di esso.
Questo funziona abbastanza abbastanza bene, tranne che i dati XML a volte, F2 viene troncato dal file. Che intendo XML incompleto, dove qualche nodo XML non sono presenti. Il problema è che non è sempre riproducibile, ed i file che vengono troncati non sono sempre gli stessi. A causa di questo, penso che mentre F1 sta scrivendo un file sul disco, F2 sta cercando di leggere lo stesso file. Ecco perché a volte ho questo tipo di errore.
La mia domanda : Mi chiedo se c'è qualche meccanismo che blocca (anche per la lettura) il file F1 sta attualmente scrivendo finché non è completamente finito di scrivere sul disco, quindi F2 non lo farà essere in grado di leggere fino a quando il file viene sbloccato. O qualsiasi altro modo per risolvere il mio problema sarà il benvenuto!
F1 sta scrivendo il file in questo modo:
try {
file = new File("some-file.xml");
FileUtils.writeStringToFile(file, xmlDataAsString);
} catch (IOException ioe) {
LOGGER.error("Error occurred while storing the XML in a file.", ioe);
}
F2 è la lettura del file in questo modo:
private File getNextFileToMap() {
File path = getPath(); // Returns the directory where F1 stores the results...
File[] files = path.listFiles(new FilenameFilter() {
public boolean accept(File file, String name) {
return name.toLowerCase().endsWith(".xml");
}
});
if (files.length > 0) {
return files[0];
}
return null;
}
// Somewhere in my main method of F2
...
f = getNextFileToMap();
Node xmlNode = null;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(f);
if (doc != null) {
xmlNode = doc.getDocumentElement();
}
} catch (Exception e) {
LOGGER.error("Error while getting the XML from the file " + f.getAbsolutePath(), e);
}
Soluzione
Dal momento che si sta già filtrando per i file .xml
in F2, hanno uscita di F1 in un file .temp
, poi rinominarlo in .xml
come passo finale. In questo modo, F2 ignorerà il file F1 sta facendo fino a quando F1 è completamente fatto con esso.
Altri suggerimenti
Avete guardato il java.nio.channels.FileLock
API?
Una soluzione più semplice potrebbe essere di scrivere un nome diverso (ad esempio foo.tmp) e poi rinominarlo (ad esempio per foo.xml) quando è pronto - la ridenominazione è atomico sulla maggior parte dei sistemi operativi (all'interno di una directory), in modo che quando l'altro processo vede il file XML deve essere integro. Questo è probabile che sia molto più semplice di chiusura.
Usa la parola chiave sincronizzato su una comune oggetto, un oggetto File che punta al file sarebbe il migliore in questo caso:
class MyFileFactory {
private static HashMap<string,File> files = new HashMap<String,File>();
public static File get(String name) {
if (!files.keyExists(name)) {
files.put(name, new File(name));
}
return files.get(name);
}
}
// Another class
synchronized(File f = MyFileFactory::get("some-file.xml")) {
// read or write here
}