Bloquear um arquivo para um tópico individual em uma VM
-
25-09-2019 - |
Pergunta
Eu tenho vários threads que estão serializando meus objetos 'dados' para arquivos. O nome do arquivo é baseado em 2 campos do objeto
class Data { org.joda.DateTime time; String title; public String getFilename() { return time.toString() + '_' + title + ".xml"; }
É possível que 2 objetos de dados tenham o mesmo 'tempo' e 'título' e, portanto, o mesmo nome de arquivo.
Isso é aceitável, e estou feliz por ser salvo. (Eles provavelmente são o mesmo objeto de dados de qualquer maneira, se forem iguais)
Meu problema é que dois (ou mais) threads estão escrevendo em um arquivo ao mesmo tempo, causando XML malformado.
Eu dei uma olhada no java.nio.channels.filelock, mas é para bloqueio de toda a VM e, especificamente, não é adequado para bloqueio intra-thread.
Eu poderia sincronizar o Dataio.class (mas isso causará uma enorme sobrecarga, já que realmente quero sincronizar apenas o arquivo individual).
A sincronização no objeto de arquivo será inútil, pois vários objetos de arquivo podem representar o mesmo arquivo do sistema.
Código segue:
class DataIO { public void writeArticleToFile(Article article, String filename, boolean overwrite) throws IOException { File file = new File(filename); writeArticleToFile(article, file, overwrite); } public void writeDataToFile(Data data, File file, boolean overwrite) throws IOException { if (file.exists()) { if (overwrite) { if (!file.delete()) { throw new IOException("Failed to delete the file, for overwriting: " + file); } } else { throw new IOException("File " + file + " already exists, and overwrite flag is set to false."); } } File parentFile = file.getParentFile(); if (parentFile != null) { file.getParentFile().mkdirs(); } file.createNewFile(); if (!file.canWrite()) { throw new IOException("You do not have permission to write to the file: " + file); } FileOutputStream fos = new FileOutputStream(file, false); try { writeDataToStream(data, fos); logger.debug("Successfully wrote Article to file: " + file.getAbsolutePath()); } finally { fos.close(); } } }
Solução
Você pode intern () a string que é o nome do arquivo. Em seguida, sincronize na sequência internada.
class DataIO {
public void writeArticleToFile(Article article, String filename, boolean overwrite) throws IOException {
synchronized(filename.intern()) {
File file = new File(filename);
writeArticleToFile(article, file, overwrite);
}
}
Outras dicas
Se estou lendo isso corretamente, você tem um objeto de dados que representa um único arquivo.
Você pode considerar a criação de um conjunto listrado com base no objeto de dados. Possivelmente tendo um mapa concorrente de
ConcurrentMap<Data,Lock> lockMap = new ConcurrentHashMap<Data,Lock>();
Não quando você deseja escrever para este objeto que você pode fazer:
Lock lock = lockMap.get(someMyDataObject);
lock.lock();
try{
//write object here
}finally{
lock.unlock();
}
Lembre -se de que você teria que escrever o método HashCode e é igual ao título e DateTime
Concordo que o uso da sincronização é a técnica que você deve usar. O que você precisa é de um objeto distinto para cada permutação de arquivo e, mais importante, o mesmo objeto a cada vez. Uma opção pode ser criar uma classe chamada FileLock:
public class FileLock {
DateTime time;
String title;
public FileLock(DateTime time, String title) {
this.time = time;
this.title = title;
}
override equals/hashCode based on those two properties
static Hashtable<FileLock, FileLock> unqiueLocks = new Hashtable<FileLock, FileLock>();
static lockObject = new Object();
public static FileLock getLock(DateTime time, String title) {
synchronized (lockObject) {
FileLock lock = new FileLock(time, title);
if (unqiueLocks.ContainsKey(lock)) {
return unqiueLocks.get(lock);
}
else {
unqiueLocks.put(lock, lock);
return lock;
}
}
}
}
Então os chamadores usariam como:
synchronized (FileLock.getLock(time, title)) {
...
}
Lembre -se de que isso tem um vazamento de memória, pois a hashtable continua crescendo com novas permutações de arquivo/tempo. Se necessário, você pode modificar essa técnica para que os chamadores de getLock também invocem um método RELEASELOCK que você usa para manter a hashtable limpa.