Блокировка файла для отдельного потока в виртуальной машине - PullRequest
2 голосов
/ 04 августа 2010

У меня есть несколько потоков, которые сериализуют мои объекты данных в файлы.Имя файла основано на 2 полях из объекта

  class Data {
    org.joda.DateTime time;
    String title;

    public String getFilename() {
      return time.toString() + '_' + title + ".xml";
    }

Возможно, что 2 объекта данных будут иметь одинаковое время и заголовок, и поэтому одно и то же имя файла.

Это приемлемо, и я рад за то, что спасся.(В любом случае это, вероятно, один и тот же объект данных, если они одинаковы)

Моя проблема в том, что два (или более) потока записывают в файл ОДНОВРЕМЕННО, вызывая неправильный XML.

Я посмотрел на java.nio.channels.FileLock, но он предназначен для блокировки VM-Wide и специально НЕ подходит для блокировки внутри потока.

Я мог синхронизироваться с DataIO.class (но это будетвызывать ОГРОМНЫЕ издержки, поскольку я действительно хочу синхронизировать только с отдельным файлом).

Синхронизация с объектом File будет бесполезной, поскольку несколько объектов File могут представлять один и тот же системный файл.

Код следует:

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

Ответы [ 3 ]

2 голосов
/ 04 августа 2010

Если я правильно читаю, у вас есть объект данных, представляющий один файл.

Вы можете создать чередующийся набор на основе объекта данных.Возможно, с ConcurrentHashMap, равным

ConcurrentMap<Data,Lock> lockMap = new ConcurrentHashMap<Data,Lock>();

Нет, когда вы хотите записать в этот объект, вы можете сделать:по названию и дате

1 голос
/ 04 августа 2010

Вы можете интернировать () строку, которая является именем файла. Затем выполните синхронизацию с интернированной строкой.

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);
    }
  }
0 голосов
/ 04 августа 2010

Я согласен, что использование синхронизации - это метод, который вы должны использовать.Вам нужен отдельный объект для каждой перестановки файлов и, что более важно, каждый раз один и тот же объект.Одним из вариантов может быть создание класса с именем 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;
            }
        }
    }
}

Тогда вызывающие абоненты будут использовать его следующим образом:

synchronized (FileLock.getLock(time, title)) {
    ...
}

Имейте в виду, что это приводит к утечке памяти, поскольку Hashtable продолжает расти сновый файл / время перестановок.Если вам нужно, вы можете изменить эту технику, чтобы вызывающие getLock также вызывали метод releaseLock, который вы используете для поддержания чистоты Hashtable.

...