Java 为什么File.exists()在多线程环境中表现不稳定?

Java 为什么File.exists()在多线程环境中表现不稳定?,java,multithreading,nio,java-io,jdk1.7,Java,Multithreading,Nio,Java Io,Jdk1.7,我有一个在JavaJDK1.7下运行的批处理过程。它运行在带有RHEL、2.6.18-308.el5#1 SMP的系统上 此进程从数据库获取元数据对象的列表。它从该元数据中提取文件的路径。该文件可能存在,也可能不存在 进程使用ExecutorService(Executors.newFixedThreadPool())启动多个线程。每个线程运行一个可调用的进程,该进程在输入文件存在时读取该文件并写入另一个文件(并记录结果),如果该文件不存在则不执行任何操作(记录该结果除外) 我发现这种行为是不确

我有一个在JavaJDK1.7下运行的批处理过程。它运行在带有RHEL、2.6.18-308.el5#1 SMP的系统上

此进程从数据库获取元数据对象的列表。它从该元数据中提取文件的路径。该文件可能存在,也可能不存在

进程使用ExecutorService(
Executors.newFixedThreadPool()
)启动多个线程。每个线程运行一个可调用的进程,该进程在输入文件存在时读取该文件并写入另一个文件(并记录结果),如果该文件不存在则不执行任何操作(记录该结果除外)

我发现这种行为是不确定的。尽管每个文件的实际存在在整个过程中都是恒定的,但运行此过程不会给出一致的结果。它通常会给出正确的结果,但偶尔会发现一些确实存在的文件并不存在。如果我再次运行相同的进程,它将发现它以前说不存在的文件

为什么会发生这种情况,有没有其他更可靠的方法?当其他线程试图读取目录时,在多线程进程中写入文件是错误的吗?较小的线程池(目前为30个)会有帮助吗

更新: 以下是此场景中工作线程调用的unix进程的实际代码:

public int convertOutputFile(String inputFile, String outputFile)
throws IOException
{
    List<String> args = new LinkedList<String>();
    args.add("sox");
    args.add(inputFile);
    args.add(outputFile);
    args.addAll(2, this.outputArguments);
    args.addAll(1, this.inputArguments);
    long pStart = System.currentTimeMillis();
    int status = -1;
    Process soxProcess = new ProcessBuilder(args).start();

    try {
        // if we don't wait for the process to complete, player won't
        // find the converted file.
        status = soxProcess.waitFor();
        if (status == 0) {
            logger.debug(String.format("SoX conversion process took %d ms.",
                    System.currentTimeMillis() - pStart));
        } else {
            logger.error("SoX conversion process returned an error status of " + status);
        }
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return status;
}
public int convertOutputFile(字符串输入文件,字符串输出文件)
抛出IOException
{
List args=new LinkedList();
参数添加(“sox”);
args.add(输入文件);
args.add(输出文件);
args.addAll(2,this.outputArguments);
args.addAll(1,this.inputArguments);
long pStart=System.currentTimeMillis();
int status=-1;
Process soxProcess=newprocessbuilder(args.start();
试一试{
//如果我们不等待过程完成,玩家就不会
//查找转换后的文件。
status=soxProcess.waitFor();
如果(状态==0){
logger.debug(String.format(“SoX转换过程耗时%d毫秒”),
System.currentTimeMillis()-pStart));
}否则{
logger.error(“SoX转换过程返回的错误状态为“+状态”);
}
}捕捉(中断异常e){
//TODO自动生成的捕捉块
e、 printStackTrace();
}
返回状态;
}
更新#2:

我尝试了从java.io.File.exists()切换到java.nio.Files.exists()的实验,这似乎提供了更高的可靠性。我还没有看到多次尝试的失败情况,与以前一样,大约有10%的时间出现了这种情况。所以我想我想知道nio版本在处理底层文件系统方面是否更加健壮这一发现后来被证明是错误的。nio在这里帮不上忙。

更新#3: 在进一步审查后,我仍然发现发生了相同的故障情况。因此,转向nio并不是灵丹妙药。通过将executor服务的线程池大小减少到1,我获得了更好的结果。这似乎更可靠,这样一来,一个线程不可能读取目录,而另一个线程启动一个进程写入同一目录

我还没有调查的另一个可能性是,将输出文件放在与输入文件不同的目录中是否会更好地为我服务。我把它们放在同一个目录中,因为它更容易编码,但这可能会让人困惑,因为输出文件的创建影响到与输入目录扫描相同的目录

更新#4:
重新编码以便将输出文件写入与输入文件(正在检查其存在性)不同的目录并没有特别的帮助唯一有帮助的更改是将ExecutorService线程池大小设置为1,换句话说,不是多线程执行此操作。

您的应用程序可能是正确的多线程,无论何时访问文件系统,它都有限制。
在您的情况下,我敢打赌有太多的线程同时访问它,其结果是FS耗尽了文件句柄。文件实例无法告诉您,因为
exists()
不会引发异常,所以即使目录存在,它们也会返回
false

这里真正的问题是为什么要调用它

  • 您必须构造一个
    FileInputStream
    FileReader
    来读取文件,如果文件无法打开,它们将抛出一个
    FileNotFoundException
    ,绝对可靠
  • 无论如何,您必须捕获异常
  • 操作系统必须检查文件是否仍然存在
  • 没有必要检查两次
  • 在检查和打开文件之间,存在可能会发生变化
所以,不要检查两次。让打开文件完成所有工作

在多线程进程中写入文件是错误的吗

我不会说这是一个错误,但这是毫无意义的。磁盘不是多线程的

较小的线程池(目前为30个)会有帮助吗


无论如何,我肯定会把这个问题减少到四个左右,不是为了解决这个问题,而是为了减少颠簸,几乎肯定会提高吞吐量。

我已经将@Olivier的答案标记为“答案”,但我在这里提供了我自己的答案,以总结我的实验结果。我称之为比任何人都更接近真相的“答案”,尽管他对文件句柄的猜测似乎并不明显正确,尽管我也无法反驳。ring true的一句简单的话是“您的应用程序可能是正确的多线程的,无论何时访问文件系统,它都有限制。