Java:如何通过线程同步文件修改

时间:2014-02-22 18:35:45

标签: java multithreading file-io synchronization

一次只能运行一个Java应用程序实例。它运行在Linux上。我需要确保一个线程不会修改文件,而另一个线程正在使用它。

我不知道要使用哪种文件锁定或同步方法。我从来没有在Java中完成文件锁定,我没有太多的Java或编程经验。

我查看了Java NIO,并且我读到“文件锁代表整个Java虚拟机。它们不适合控制同一虚拟机中多个线程对文件的访问。”我知道我需要专家帮助,因为这是生产代码,我几乎不知道我在做什么(我今天必须完成它)。

以下是我将代码(存档文件)上传到服务器的代码的简要概述。它获取要从文件上传的文件列表(称之为“listFile”) - 并且可以在此方法读取时修改listFile。我通过将listFile复制到临时文件并在此后使用该临时文件来最小化这种可能性。但我认为我需要在复制过程中锁定文件(或类似的东西)。

package myPackage;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import com.example.my.FileHelper;
import com.example.my.Logger;

public class BatchUploader implements Runnable {

    private int processUploads() {
        File myFileToUpload;
        File copyOfListFile = null;
        try {
            copyOfListFile = new File("/path/to/temp/workfile");
            File origFile = new File("/path/to/listFile"); //"listFile" - the file that contains a list of files to upload
            DataWriter.copyFile(origFile, copyOfListFile);//see code below
        } catch (IOException ex) {
            Logger.log(ex);
        }
        try {
            BufferedReader input = new BufferedReader(new FileReader(copyOfListFile));
            try {
                while (!stopRunning && (fileToUploadName = input.readLine()) != null) {
                    upload(new File(fileToUploadName));
                }
            } finally {
                input.close();
                isUploading = false;
            }

        }
        return filesUploadedCount;
    }
}

以下是修改上述代码中使用的文件列表的代码:

public class DataWriter {

    public void modifyListOfFilesToUpload(String uploadedFilename) {

        StringBuilder content = new StringBuilder();

        try {
            File listOfFiles = new File("/path/to/listFile"); //file that contains a list of files to upload
            if (!listOfFiles.exists()) {
                //some code
            }

            BufferedReader input = new BufferedReader(new FileReader(listOfFiles));
            try {
                String line = "";
                while ((line = input.readLine()) != null) {
                    if (!line.isEmpty() && line.endsWith(FILE_EXTENSION)) {
                        if (!line.contains(uploadedFilename)) {
                            content.append(String.format("%1$s%n", line));
                        } else {
                            //some code
                        }
                    } else {
                        //some code
                    }
                }
            } finally {
                input.close();
            }
            this.write("/path/to/", "listFile", content.toString(), false, false, false);
        } catch (IOException ex) {
            Logger.debug("Error reading/writing uploads logfile: " + ex.getMessage());
        }
    }

    public static void copyFile(File in, File out) throws IOException {
        FileChannel inChannel = new FileInputStream(in).getChannel();
        FileChannel outChannel = new FileOutputStream(out).getChannel();
        try {
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } catch (IOException e) {
            throw e;
        } finally {
            if (inChannel != null) {
                inChannel.close();
            }
            if (outChannel != null) {
                outChannel.close();
            }
        }
    }

    private void write(String path, String fileName, String data, boolean append, boolean addNewLine, boolean doLog) {
        try {
            File file = FileHelper.getFile(fileName, path);
            BufferedWriter bw = new BufferedWriter(new FileWriter(file, append));
            bw.write(data);
            if (addNewLine) {
                bw.newLine();
            }
            bw.flush();
            bw.close();
            if (doLog) {
                Logger.debug(String.format("Wrote %1$s%2$s", path, fileName));
            }
        } catch (java.lang.Exception ex) {
            Logger.log(ex);
        }

    }
}

2 个答案:

答案 0 :(得分:2)

我的建议采用略有不同的方法。在Linux上,文件重命名(mv)操作在本地磁盘上是原子的。一个进程没有机会看到“半写”文件。

设XXX为具有三个(或更多)数字的序列号。您可以将DataWriter附加到名为listFile-XXX.prepare的文件中,并将固定数量N的文件名写入其中。写入N个名称时,关闭该文件并将其重命名(原子,见上文)到listFile-XXX。使用下一个文件名,开始写入listFile-YYY,其中YYY = XXX + 1。

您的BatchUploader可以随时检查是否找到与模式listFile-XXX匹配的文件,打开它们,读取它们上传指定文件,关闭并删除它们。线程没有机会弄乱对方的文件。

实施提示:

  1. 确保在BatchUploader中使用轮询机制,如果找不到准备上传的文件(等待空闲等待),则等待1秒或更长时间。
  2. 您可能需要确保根据XXX对listFile-XXX进行排序,以确保按顺序保持上传。
  3. 当然,您可以修改listFile-XXX.prepare关闭时的协议。如果DataWriter在较长时间内无事可做,那么您不希望文件准备好上传,因为还没有准备就绪。
  4. 好处:没有锁定(这样做会很难),没有复制,工作队列的简单概述以及文件系统中的状态。

答案 1 :(得分:1)

这是一个略有不同的建议。假设你的文件名中没有'\ n'字符(这是linux上的一个很大的假设,我知道,但是你可以让你的作者看到它),为什么不只读完整行并忽略不完整的行?通过不完整的行,我的意思是以EOF结尾而不是\ n。

的行

修改:请在下面的评论中查看更多建议。