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();
    }
  }
}
Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top