首页 > 解决方案 > 防止从多个线程访问时删除文本文件

问题描述

我正在尝试调试刚刚出现在我的程序中的问题。到目前为止,我一直在使用以下代码结构毫无问题地编写、读取和更新 props 文件:

public void setAndReplacePropValue(String dir, String key, String value) throws FileNotFoundException, IOException {

    if (value != null) {
     File file = new File(dir);
     if (!file.exists()) {
                System.out.println("File: " + dir + " is not present. Attempting to create new file now..");
                new FilesAndFolders().createTextFileWithDirsIfNotPresent(dir);
     }

     if (file.exists()) {
        try {
            FileInputStream fileInputStream = null;
            fileInputStream = new FileInputStream(file);
            if (fileInputStream != null) {
                Properties properties = new Properties();
                properties.load(fileInputStream);
                fileInputStream.close();

              if (properties != null) {
               FileOutputStream fileOutputStream = new FileOutputStream(file);
                properties.setProperty(key, value);
                properties.store(fileOutputStream, null);
                fileOutputStream.close();
                }
            }   
        }
        catch (Exception e) {
         e.printStackTrace();
        }
    } else {
            System.out.println("File: " + dir + " does not exist and attempt to create new file failed");
            }
        }
    }

但是,最近我注意到一个特定的文件(我们称之为 C:\\Users\\Admin\\Desktop\\props.txt:)在从多个线程更新后被删除。我不确定这个错误的确切来源,因为它似乎是随机发生的。

我认为,也许,如果两个线程调用setAndReplacePropValue()并且第一个线程 FileOutputStream fileOutputStream = new FileOutputStream(file);在它有机会将数据重新写入文件(通过 properties.store(fileOutputStream, null))之前调用,那么第二个线程可能会调用fileInputStream = new FileInputStream(file);一个空文件 - 导致线程在写入时删除以前的数据“空”数据返回文件。

为了验证我的假设,我尝试setAndReplacePropValue()从多个线程连续调用数百到数千次,同时setAndReplacePropValue()根据需要进行更改。这是我的结果:

  1. 如果setAndReplace()声明为static+ ,则保留synchronized原始数据。props即使我在调用后添加随机延迟,这仍然是正确的FileOutputStream——只要 JVM 正常存在。如果 JVM 被杀死/终止(在FileOutputStream被调用之后),那么之前的数据将被删除。

  2. 如果我同时删除staticandsynchronized修饰符setAndReplace()并调用setAndReplace()5,000 次,旧数据仍会保留(为什么?) - 只要 JVM 正常结束。即使我在setAndReplace()(调用后FileOutputStream)添加随机延迟,这似乎也是正确的。

  3. 当我尝试使用ExecutorService(我偶尔在我的程序中访问setAndReplacePropValue()via ExecutorService)修改 props 文件时,只要在FileOutputStream. 如果我添加延迟并且延迟是在 future.get() 中设置的 > 'timout' 值(因此interrupted exception被抛出),则不会保留数据。即使我在方法中添加static+synchronized关键字,这仍然是正确的。

简而言之,我的问题是为什么文件被删除最可能的解释是什么?(我认为第 3 点可能会解释错误,但我实际上并没有sleeping在调用之后,new FileOutputStream()所以大概这不会阻止数据在调用之后被写回文件new FileOutputStream())。有没有我没有想到的另一种可能性?

另外,为什么第2点是真的?如果方法未声明为静态/同步,这不应该导致一个线程InputStream从空文件创建吗?谢谢。

标签: javamultithreadingconcurrencyio

解决方案


不幸的是,如果没有大量附加信息,很难就您的代码提供反馈,但希望我的评论会有所帮助。

一般来说,让多个线程从同一个文件中读取和写入是一个非常糟糕的主意。我完全同意@Hovercraft-Full-Of-Eels,他建议您有1 个线程进行读/写,而其他线程只是将更新添加到 shared BlockingQueue

但是,这里有一些评论。

如果 setAndReplace() 声明为静态 + 同步,则保留原始道具数据。

对,这停止了代码中可怕的竞争条件,其中 2 个线程可能同时尝试写入输出文件。或者可能是 1 个线程开始写入,而另一个线程读取了一个空文件,导致数据丢失。

如果 JVM 被杀死/终止(在 FileOutputStream 被调用之后),那么之前的数据将被删除。

我不太了解这部分,但是您的代码应该有很好的 try/finally 子句,以确保在 JVM 终止时正确关闭文件。如果 JVM 被硬杀死,则文件可能已打开但尚未写入(取决于时间)。在这种情况下,我建议您写入一个临时文件并重命名为您的原子属性文件。然后,如果 JVM 被杀死,您可能会错过更新,但文件永远不会被覆盖并且为空。

如果我从 setAndReplace() 中删除静态和同步修饰符并调用 setAndReplace() 5,000 次,旧数据仍然保留(为什么?)

不知道。取决于比赛条件。也许你只是走运了。

当我尝试使用 ExecutorService 修改 props 文件时(我偶尔会在我的程序中通过 ExecutorService 访问 setAndReplacePropValue()),只要在 FileOutputStream 之后没有延迟,文件内容就会被保留。如果我添加延迟并且延迟>在 future.get() 中设置的“超时”值(因此引发中断的异常),则不会保留数据。即使我在方法中添加静态 + 同步关键字,这仍然是正确的。

如果没有看到具体的代码,我无法回答这个问题。

实际上,如果您有一个带有 1 个线程的固定线程池,那么这将是一个好主意,那么每个想要更新值的线程只需将字段/值对象提交到线程池。这大概就是@Hovercraft-Full-Of-Eels 所说的。

希望这可以帮助。


推荐阅读