同时创建新文件

时间:2015-09-15 09:57:04

标签: java windows multithreading file

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

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

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

不确定问题是否与this question中的问题相同,但可以。在我看来,在同一过程中使用多个线程似乎是问题的一部分,但我无法确定,但它的再现速度更快。

2 个答案:

答案 0 :(得分:5)

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

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附加到文件名。

答案 1 :(得分:0)

你的循环不是故障安全的。这是一个时间窗问题。应该更像这样:

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