【问题标题】:Creating new files concurrently [duplicate]同时创建新文件[重复]
【发布时间】:2015-09-15 09:57:03
【问题描述】:

要创建一个新的、唯一的文件名,我使用以下代码:

File file = new File(name);
synchronized (sync) {
    int cnt = 0;
    while (file.exists()) {
        file = new File(name + " (" + (cnt++) + ")");
    }
    file.createNewFile();
}

接下来,我使用该文件并将其删除。 当我在多线程情况下执行此操作时,有时会在 file.createNewFile() 上遇到异常:

java.io.IOException: Access is denied
    at java.io.WinNTFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:1012)

以下代码重现了该问题(大多数情况下):

final int runs = 1000;
final int threads = 5;
final String name = "c:\\temp\\files\\file";
final byte[] bytes = getSomeBytes();
final Object sync = new Object();

ExecutorService exec = Executors.newFixedThreadPool(threads);
for (int thread = 0; thread < threads; thread++) {
    final String id = "Runnable " + thread;
    exec.execute(new Runnable() {
        public void run() {
            for (int i = 0; i < runs; i++) {
                try {
                    File file = new File(name);
                    synchronized (sync) {
                        int cnt = 0;
                        while (file.exists()) {
                            file = new File(name + " (" + (cnt++) + ")");
                        }
                        file.createNewFile();
                    }

                    Files.write(file.toPath(), bytes);
                    file.delete();
                } catch (Exception ex) {
                    System.err.println(id + ": exception after " + i
                            + " runs: " + ex.getMessage());
                    ex.printStackTrace();
                    return;
                }
            }
            System.out.println(id + " finished fine");
        }
    });
}
exec.shutdown();
while (!exec.awaitTermination(1, TimeUnit.SECONDS));

getSomeBytes()方法只是生成一个字节数,实际内容并不重要:

byte[] getSomeBytes() throws UnsupportedEncodingException,
        IOException {
    byte[] alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ1234567890\r\n"
            .getBytes("UTF-8");
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        for (int i = 0; i < 100000; i++) {
            baos.write(alphabet);
        }
        baos.flush();
        return baos.toByteArray();
    }
}

当我执行这段代码时,它有时运行良好,但大多数时候,它会产生一些异常,例如下面的输出:

Runnable 1: exception after 235 runs: Access is denied
java.io.IOException: Access is denied
    at java.io.WinNTFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:1012)
    at test.CreateFilesTest$1.run(CreateFilesTest.java:36)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Runnable 4: exception after 316 runs: Access is denied
java.io.IOException: Access is denied
    at java.io.WinNTFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:1012)
    at test.CreateFilesTest$1.run(CreateFilesTest.java:36)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Runnable 2: exception after 327 runs: Access is denied
java.io.IOException: Access is denied
    at java.io.WinNTFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:1012)
    at test.CreateFilesTest$1.run(CreateFilesTest.java:36)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Runnable 3 finished fine
Runnable 0 finished fine

有什么想法吗?我已经在带有 java 1.7.0_45 和 1.8.0_31 的 Windows 8 机器上进行了测试,结果相同。

不确定问题是否与this question 相同,但可以。在我看来,在同一进程中使用多个线程似乎是问题的一部分,但我不能确定,但​​是它的复制速度更快。

【问题讨论】:

  • File.createTempFile() 在这里可能是一种更简洁的方法,无论文件是否实际上是临时文件。
  • @Sneftel:我同意,但是文件名很重要,所以我不能在这里使用 File.createTempFile

标签: java windows multithreading file


【解决方案1】:

似乎在 Windows 平台上createNewFile 可能会随机失败,如果即使在单线程应用程序上刚刚删除了同名文件。有关详细信息,请参阅this question。要解决您的问题,您可以尝试忽略createNewFile 中的IOException 并继续。像这样的:

synchronized (sync) {
    int cnt = 0;
    while (true) {
        try {
            if(file.createNewFile())
                break;
        } catch (IOException e) {
            // continue;
        }
        file = new File(name + " (" + (cnt++) + ")");
    }
}

请注意,您不需要检查file.exists() 调用,因为createNewFile() 方便地返回它是否成功创建文件。

请注意,如果您控制您创建的所有临时文件并且不关心确切的文件名,通常不需要锁定。您可以只使用全局 AtomicLong 来获取下一个文件名或在文件名后附加线程 ID。

【讨论】:

  • 由于问题可能是由外部因素引起的,我想在这种情况下我会尝试/解决问题
【解决方案2】:

您的循环不是故障安全的。存在时间窗口问题。它应该更像这样:

while (!file.createNewFile()) {
        file = new File(name + " (" + (cnt++) + ")");
    }

【讨论】:

  • 您能否详细说明一下这个同步部分中的“时间窗口”是什么?此外,您的固定代码也会以与 OP 代码相同的方式失败。
  • @TagirValeev 在exists()createNewFile() 之间有一个时间窗口,在此期间文件可以由另一个线程创建。也有检查createNewFile()的结果失败。如果我在此处发布的两行循环失败,则必须存在平台问题,此方法无法像宣传的那样工作。实际上,此循环与您的答案中的代码之间没有区别,而不是 catch 块。你怎么解释?
  • 请注意,所有线程都在同一个监视器上同步,因此另一个线程无法做到这一点。
  • @TagirValeev 所以另一个进程可以创建它。仍然存在时间窗口问题。
猜你喜欢
  • 2010-12-13
  • 1970-01-01
  • 2018-03-17
  • 2020-08-07
  • 1970-01-01
  • 1970-01-01
  • 2010-11-17
  • 2015-04-01
  • 1970-01-01
相关资源
最近更新 更多