2015-09-15 12 views
5

要創建一個新的,唯一的文件名,我用下面的代碼:創建新的文件,同時

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(); 
    } 
} 

當我執行這個代碼時,它有點ES順利,但大多數時候,它會產生一些例外像下面的輸出,例如:

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相同,但可以。在同一個過程中使用多個線程似乎是我認爲的問題的一部分,但我無法確定,但它的速度更快。

+2

'File.createTempFile()'在這裏可能是一個更清潔的方法,不管文件是否實際上是臨時的。 – Sneftel

+0

@Sneftel:但是,我同意文件的名稱很重要,所以我不能在這裏使用File.createTempFile – Steven

回答

5

似乎在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。

+0

由於問題可能是由外部因素引起的,我想我會在這種情況下嘗試/解決問題 – Steven

0

您的環路不是故障安全的。有一個時間窗口問題。它應該是更多這樣的:

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

請您在這個同步部分的「時間窗口」上稍微詳細一點?您的固定代碼也會以與OP代碼相同的方式失敗。 –

+0

@TagirValeev'exists()'和'createNewFile()'之間有一個時間窗口,在此窗口中可以由另一個線程創建文件。也沒有檢查'createNewFile()'的結果。如果我在這裏發佈的雙線循環失敗,則此方法的平臺問題不能像宣傳的那樣工作。事實上,在這個循環和你的答案中的代碼之間沒有區別。你如何解釋? – EJP

+1

請注意,所有線程在同一個監視器上同步,因此另一個線程無法執行此操作。 –