【问题标题】:Java: how to handle two process trying to modify the same file [duplicate]Java:如何处理试图修改同一个文件的两个进程[重复]
【发布时间】:2018-03-02 08:36:51
【问题描述】:

可能重复:
How can I lock a file using java (if possible)

我有两个进程调用两个修改同一个文本文件的 Java 程序。我注意到文本文件的内容缺少数据。我怀疑当一个java程序获得文本文件的写入流时,我认为它会阻止另一个java程序修改它(比如当你打开一个文件时,你不能删除那个文件)。除了数据库,有没有办法解决这个问题? (并不是说 db 解决方案不干净或不优雅,只是我们在操作这个文本文件时写了很多代码)

编辑

事实证明,我在定位问题时犯了一个错误。我的文本文件中的数据丢失的原因是,

ProcessA:不停的往文本文件中添加行数据

ProcessB:开始时,将文本字段的所有行加载到List中。然后操作该列表的包含。最后,ProcessB 将列表写回,替换文本文件的包含。

这在顺序过程中效果很好。但是一起运行的时候,如果ProcessA向文件中添加数据,在ProcessB操作List的时候,那么当ProcessBList写出来的时候,不管ProcessA加什么,都会被覆盖.所以我最初的想法是在ProcessB 写回List 之前,在文本文件和List 之间同步数据。所以当我写出List 时,它将包含所有内容。所以这是我的努力

public void synchronizeFile(){
    try {
        File file = new File("path/to/file/that/both/A/and/B/write/to");
        FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
        FileLock lock = channel.lock(); //Lock the file. Block until release the lock
        List<PackageLog> tempList = readAllLogs(file);
        if(tempList.size() > logList.size()){
            //data are in corrupted state. Synchronized them.
            for(PackageLog pl : tempList){
                if(!pl.equals(lookUp(pl.getPackageLabel().getPackageId(), pl.getPackageLabel().getTransactionId()))){
                    logList.add(pl);
                }
            }
        }
        lock.release(); //Release the file
        channel.close();
    } catch (IOException e) {
        logger.error("IOException: ", e); 
    }
}

所以logListProcessB 想要写出的当前列表。所以在写出之前,我读取文件并将数据存储到tempList,如果tempListlogList不一样,同步它们。问题是此时ProcessAProcessB 当前都在访问该文件,所以当我尝试锁定文件并从中读取List&lt;PackageLog&gt; tempList = readAllLogs(file); 时,我要么得到OverlappingFileLockException,要么得到java.io.IOException: The process cannot access the file because another process has locked a portion of the file。请帮我解决这个问题:(

EDIT2:我对Lock的理解

public static void main(String[] args){
    File file = new File("C:\\dev\\harry\\data.txt");

    FileReader fileReader = null;
    BufferedReader bufferedReader = null;
    FileChannel channel = null;
    FileLock lock = null;
    try{
        channel  = new RandomAccessFile(file, "rw").getChannel();
        lock = channel.lock();
        fileReader = new FileReader(file);
        bufferedReader = new BufferedReader(fileReader);
        String data;
        while((data = bufferedReader.readLine()) != null){
            System.out.println(data);
        }
    }catch(IOException e){
        e.printStackTrace();
    }finally{
        try {
            lock.release();
            channel.close();
            if(bufferedReader != null) bufferedReader.close();
            if(fileReader != null) fileReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我收到了这个错误IOException: The process cannot access the file because another process has locked a portion of the file

【问题讨论】:

  • @Vineet:锁定文件肯定会解决reader - writer的问题,但我不确定writer - writer的问题。我会试着写一些代码来测试一下。
  • 它会解决问题,只要您的代码事先不假设它必须在文件中的位置 X 处写入。您可以获取 FileLock,然后确定要写入的适当位置(可能在读取文件之后)。
  • @Vineet:当你锁定一个文件时,如果另一个进程进来并访问该文件,该进程会挂在那里等待锁定还是会产生IOException?
  • 这取决于您在 FileChannel 上进行的调用。如果通过 API,lock() 是一个阻塞调用,而 tryLock() 不会阻塞。此外,这取决于所获取锁的性质。还需要考虑某些平台依赖性; FileLock 文档指出:“锁是否真正阻止另一个程序访问锁定区域的内容取决于系统,因此未指定。”。
  • @Vineet:我发现了我真正的问题,所以如果可能的话,请看看我的原始帖子(编辑部分)。我已经看了好几个小时了。 tyvm

标签: java io


【解决方案1】:

因此,您可以使用 Vineet Reynolds 在他的评论中建议的方法。

如果这两个进程实际上只是同一应用程序中的单独线程,那么您可以在某处设置一个标志以指示文件已打开。

如果它完全是两个独立的应用程序/进程,则底层文件系统应该锁定文件。当您从输出流中收到 I/O 错误时,您应该能够围绕它包装一个 try/catch 块,然后将您的应用设置为稍后重试,或者为您的特定应用提供任何所需的行为。

文件并非真正设计为由多个应用程序同时写入。如果您可以描述为什么要从多个进程同时写入文件,那么可能会建议其他解决方案。


您最近编辑后的更新: 好的,所以您至少需要 3 个文件来完成您所说的操作。您绝对不能尝试同时将数据读/写到单个文件。您的三个文件是:

  1. ProcessA 将新/传入数据转储到的文件
  2. ProcessB 当前正在处理的文件
  3. 保存 ProcessB 输出的最终“输出”文件。

ProcessB的循环:

  • 获取文件#2 中的任何数据,对其进行处理,然后将输出写入文件#3
  • 删除文件#2
  • 重复

ProcessA的循环:

  • 将所有新的传入数据写入文件#1
  • 定期检查文件#2 是否存在
  • 当文件#2 被 ProcessB 删除时,ProcessA 应该停止写入文件#1,将文件#1 重命名为文件#2
  • 开始一个新文件#1
  • 重复

【讨论】:

  • 我知道这篇文章已经被其他人关闭了,但是我已经弄清楚了我真正的问题,并用解释和代码更新了我的原始帖子。你认为你可以看看它并给我一些启示吗? tyvm.
  • 根据您最近的更新,我已经更新了我的答案。你绝对不能用一个文件来做你所描述的事情。但是使用 3 个文件很容易做到这一点,而且不会增加太多复杂性。
  • 非常感谢,我觉得这样很干净。再次 tyvm。
【解决方案2】:

如果这是两个单独的应用程序试图访问该文件。一个会通过 IOException 因为他无法访问它。如果发生这种情况,请在 catch(IOException err){} 添加代码以暂停当前线程几毫秒,然后递归地尝试再次写入 - 直到它获得访问权限。

public boolean writeFile()
{
    try
    {
       //write to file here
        return true;
    }
    catch (IOException err) // Can't access
    {
        try
        {
            Thread.sleep(200); // Sleep a bit
            return writeFile(); // Try again
        }
        catch (InterruptedException err2)
        {
           return writeFile(); // Could not sleep, try again anyway
        }
    }
}

这将继续尝试,直到您收到StackOverflow Exception,这意味着它进入了太深;但是在这种情况下发生这种情况的可能性很小 - 只有当文件被其他应用程序保持打开很长时间时才会发生。

希望这会有所帮助!

【讨论】:

  • 我知道这篇文章已经被其他人关闭了,但是我已经弄清楚了我真正的问题,并用解释和代码更新了我的原始帖子。你认为你可以看看它并给我一些启示吗? tyvm.
  • @Harry Pham - ok,再看看。
  • 不,抱歉,我对 FileLock 等一无所知。
【解决方案3】:

更新问题中的代码很可能是进程 B 的代码,而不是进程 A 的代码。我假设是这种情况。

考虑到引发了OverlappingFileLockException 异常的实例,似乎同一进程中的另一个线程正在尝试锁定同一文件。这不是 A 和 B 之间的冲突,而是 B 内部的冲突,如果按照 API 文档中关于 lock() 方法的情况以及当它抛出 OverlappingFileLockException 的条件:

如果一个锁与请求的重叠 区域已被此 Java 持有 虚拟机,或者如果另一个线程 已在此方法中被阻止并且 正在尝试锁定重叠 同一文件的区域

防止这种情况的唯一解决方案是阻止 B 中的任何其他线程获取对同一文件或文件中相同重叠区域的锁定。

被抛出的IOException 有一个更有趣的消息。它可能证实了上述理论,但没有查看整个源代码,我无法确认任何事情。 lock 方法预计会阻塞,直到获得排他锁。如果它被获取,那么写入文件应该没有问题。一种情况除外。如果文件已经被不同线程中的同一个 JVM 打开(并锁定),使用 File 对象(或者换句话说,第二个/不同的文件描述符),那么尝试写入第一个文件描述符将失败,即使如果获得了锁(毕竟锁不会锁住其他线程)。

一个改进的设计是在每个进程中都有一个线程,该线程仅在一定时间内获取文件的排他锁(同时使用单个文件对象或单个文件描述符),执行所需的文件中的activity,然后释放锁。

【讨论】:

  • 感谢您的回复,尽管该帖子已被其他人关闭。我将查看我的代码并尝试您的建议,但是,上述方法是我创建锁的唯一地方。其他地方,我只是打开并写入文件。奇怪的是它说它已经被锁定在这条线上List&lt;PackageLog&gt; tempList = readAllLogs(file);,但这是我第一次锁定它。其他进程仍然打开文件,但肯定没有锁定文件。
  • @Harry,这就是我所说的其他描述符可能是打开的。如果它们继续打开,则获取锁并尝试写入将导致 IOException。 PS:我会投票重新提出这个问题,因为当前的问题与您提出的问题不同。
  • @Vineet:我决定编写一些测试代码来测试我对Lock 的理解,显然,我错了。我用EDIT2 更新了我的原始帖子。请你看看他们,让我知道我做错了什么吗?
  • 错误,发布一个新问题。显然,我现在是唯一一个正在查看您的代码的人。
  • 我同意,抱歉,这是stackoverflow.com/questions/6345118/… 的新问题。非常感谢,你帮了很多忙。
【解决方案4】:

使用 MapReduce 思维来思考这个问题。让我们假设每个程序都在写入输出而不读取其他程序的输出。我会写两个单独的文件,然后有一个“减少”阶段。您的减少可能是一个简单的按时间顺序的合并。

但是,如果您的程序需要彼此输出。你有一个非常不同的问题,需要重新考虑如何划分工作。

最后,如果两个程序的输出相似但独立,并且您将其写入一个文件以便第三个程序可以读取所有内容,请考虑更改第三个程序以读取这两个文件。

【讨论】:

    猜你喜欢
    • 2020-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-31
    • 1970-01-01
    相关资源
    最近更新 更多