在将文件写入磁盘时锁定该文件

3
我有两个独立的线程F1和F2(确切地说是java.util.concurrent.FutureTask的两个实例),它们并行运行。
F1执行一些处理,然后将结果复制到XML文件中。然后,它重复这些步骤,直到没有任务需要处理(会创建许多XML文件)。F2查找F1的输出目录,获取一个文件,解析它并对其执行一些处理。
这种方法相当有效,但有时F2从文件中获取截断的XML数据。我的意思是不完整的XML,其中某些XML节点不存在。问题在于,这种情况并不总是可以重现,并且被截断的文件也不总是相同的。因此,我认为当F1正在将一个文件写入磁盘时,F2正在尝试读取同一个文件。这就是为什么有时会出现这种错误的原因。
我的问题是:我想知道是否有一种机制可以锁定(即使是只读)F1当前正在写入的文件,直到它完成将其写入磁盘,这样F2就无法读取该文件,直到该文件被解锁。或者任何其他解决我的问题的方法都将受到欢迎!
F1使用以下方式编写文件:
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这样读取文件:

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);
}
3个回答

7
你看过java.nio.channels.FileLockAPI吗?
一个更简单的解决方案可能是先写入一个不同的文件名(例如foo.tmp),然后在准备好时将其重命名(例如为foo.xml) - 在大多数操作系统上,重命名在目录内是原子性的,因此当其他进程看到XML文件时,它应该是完整的。这比锁定要简单得多。

这可能是这个经典问题的最佳答案!非常感谢! - Gaurav

6

既然你已经在 F2 中筛选 .xml 文件,那么就让 F1 输出到一个 .temp 文件,最后再将其重命名为 .xml。这样,F2 将会忽略 F1 正在处理的文件,直到 F1 处理完毕。


啊,被6秒打败了...由于涉及到FileLock的引用,我暂时保留我的答案。 - Jon Skeet
@Jon Skeet:我本来想编辑一下提到锁的内容。我们有相同的想法,只是顺序不同而已。 - Welbog
为什么我要寻找复杂的解决方案,而实际上有一个如此简单的东西呢?感谢这个想法! - Romain Linsolas

2

在这种情况下,使用关键字synchronized来锁定一个公共对象,指向该文件的File对象是最好的选择:

 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
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接