有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

io Java:如何处理试图修改同一文件的两个进程

Possible Duplicate:
How can I lock a file using java (if possible)

我有两个进程,调用修改同一文本文件的两个Java程序。我注意到文本文件的内容缺少数据。我怀疑当一个java程序获得文本文件的写入流时,它会阻止另一个java程序对其进行修改(就像打开一个文件时,不能删除该文件)。除了数据库之外,还有其他解决方法吗?(并不是说db解决方案不干净也不优雅,只是我们在处理这个文本文件时编写了很多代码)

编辑

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

ProcessA:保持将数据行添加到文本文件

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

这在顺序过程中非常有效。但是当一起运行时,如果ProcessAProcessB操作List期间向文件添加数据,那么当ProcessB写回List时,不管ProcessA刚刚添加的是什么,都将被覆盖。所以我最初的想法是在ProcessBList之前,在文本文件和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<PackageLog> tempList = readAllLogs(file);时,我要么得到OverlappingFileLockException,要么得到java.io.IOException: The process cannot access the file because another process has locked a portion of the file。请帮助我解决此问题:(

EDIT2:我对锁的理解

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


共 (4) 个答案

  1. # 1 楼答案

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

    考虑到抛出了OverlappingFileLockException异常的一个实例,同一进程中的另一个线程似乎正在尝试锁定同一文件。这不是a和B之间的冲突,而是B内部的冲突,如果查看lock()方法上的API文档,并且当它抛出OverlappingFileLockException时:

    If a lock that overlaps the requested region is already held by this Java virtual machine, or if another thread is already blocked in this method and is attempting to lock an overlapping region of the same file

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

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

    一种改进的设计是,在每个进程中都有一个线程,该线程仅在一段时间内(使用单个文件对象或单个文件描述符)获取文件的独占锁,在文件中执行所需的活动,然后释放锁

  2. # 2 楼答案

    所以,你可以使用维尼特·雷诺兹在评论中建议的方法

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

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

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


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

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

    ProcessB的循环:

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

    ProcessA循环:

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

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

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

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

  4. # 4 楼答案

    如果这是两个独立的应用程序试图访问该文件。那个人会通过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意味着它进入太深;但在这种情况下发生这种情况的可能性很小——只有当文件被其他应用程序长时间打开时才会发生

    希望这有帮助