防止从多个线程访问文本文件时被删除

时间:2019-03-05 02:34:43

标签: java multithreading concurrency io

我正在尝试调试程序中刚刚出现的问题。到目前为止,我一直在使用以下代码结构编写,读取和更新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. 如果我同时从static中删除synchronizedsetAndReplace()修饰符并调用setAndReplace() 5,000次,则旧数据仍会保留(为什么? )-只要JVM正常结束。即使我在setAndReplace()中添加了随机延迟(在调用FileOutputStream之后),这似乎也是正确的。

  3. 当我尝试使用ExecutorService修改props文件(我偶尔会通过程序中的setAndReplacePropValue()访问ExecutorService)时,只要{ {1}}。如果添加延迟,并且延迟>在future.get()中设置的'timout'值(因此引发FileOutputStream),则将保留数据。即使我将interrupted exception + static关键字添加到方法中,也是如此。

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

还有,为什么第2点是正确的?如果未将method声明为static / synchronized,这是否不应该导致一个线程从空文件创建new FileOutputStream()?谢谢。

1 个答案:

答案 0 :(得分:0)

不幸的是,在没有大量其他信息的情况下,很难对您的代码提供反馈,但是希望我的评论会有所帮助。

通常,让多个线程从同一文件读取和写入是一个非常糟糕的主意。我完全同意@ Hovercraft-Full-Of-Eels,他建议您使用 1 线程进行读/写操作,而其他线程则仅将更新添加到共享的BlockingQueue中。

但这就是这里的一些评论。

  

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

正确,这将停止代码中可怕的争用条件,在该条件下,两个线程可能试图同时写入输出文件。或者可能是一个线程开始进行写入,而另一个线程读取了一个空文件,导致数据丢失。

  

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

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

  

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

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

  

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

如果不查看特定代码,我无法回答。

实际上,如果您有一个带有1个线程的固定线程池,那么这将是一个好主意,然后每个要更新值的线程都将字段/值对象提交给线程池。这大约是@ Hovercraft-Full-Of-Eels所谈论的。

希望这会有所帮助。