2015-09-15 9 views
5

Aby utworzyć nową, unikalną nazwę pliku, używam następujący kod:tworzeniu nowych plików jednocześnie

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

Dalej używam plik i go usunąć. Kiedy to zrobić w wielowątkowym sytuacji, ja czasami wyjątki na file.createNewFile():

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

Poniższy kod reprodukuje problemu (w większości przypadków):

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

Sposób getSomeBytes() tylko generuje ilość bajtów, rzeczywista treść nie jest ważna:

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

Kiedy wykonuję ten kod, to czasami es idzie dobrze, ale przez większość czasu generuje pewne wyjątki, takie jak wyjście poniżej na przykład:

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 

Jakieś pomysły? Przetestowałem na komputerze z Windows 8 z java 1.7.0_45 i 1.8.0_31, oba te same wyniki.

Nie wiem, czy problem jest taki sam jak w przypadku this question, ale może być. Używanie wielu wątków w tym samym procesie wydaje się być częścią mojego problemu, ale nie mogę być tego pewien, ale jest to szybsze odtworzenie.

+2

'File.createTempFile()' może być tu metodą czystszą, niezależnie od tego, czy plik ma być tymczasowy. – Sneftel

+0

@Sneftel: Zgadzam się jednak, nazwa pliku ma znaczenie, więc nie mogę użyć File.createTempFile tutaj – Steven

Odpowiedz

5

Wydaje się, że na platformie Windows createNewFile może losowo niepowodzeniem, jeśli plik o tej samej nazwie został właśnie usunięty nawet na jednowątkowy aplikacji. Aby uzyskać szczegółowe informacje, patrz this question. Aby rozwiązać problem, możesz spróbować zignorować numer IOException z createNewFile i kontynuować. Coś takiego:

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

Zauważ, że nie ma potrzeby, by sprawdzić file.exists() wezwanie jako createNewFile() dogodnie zwraca czy utworzony plik pomyślnie.

Pamiętaj, że jeśli kontrolujesz wszystkie tworzone pliki tymczasowe i nie zależy Ci na dokładnej nazwie pliku, zwykle nie ma potrzeby blokowania. Możesz użyć globalnej nazwy AtomicLong, aby uzyskać następną nazwę pliku lub dołączyć identyfikator wątku do nazwy pliku.

+0

Ponieważ problem jest prawdopodobnie spowodowany czynnikiem zewnętrznym, myślę, że spróbuję/wyłapać się z problemu w tym przypadku – Steven

0

Twoja pętla nie jest bezpieczna. Wystąpił problem z zakresem czasu. Powinno być więcej tak:

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

Czy mógłbyś być nieco bardziej szczegółowy na temat "okna czasowego" w tej zsynchronizowanej sekcji? Również Twój ustalony kod kończy się niepowodzeniem, tak samo jak kod OPs. –

+0

@TagirValeev Istnieje okno czasowe pomiędzy 'exists()' i 'createNewFile()', podczas którego plik może zostać utworzony przez inny wątek. Istnieje również niepowodzenie w sprawdzeniu wyniku 'createNewFile()'. Jeśli opublikowana tutaj pętla dwuprzewodowa nie powiedzie się, musi być problem z platformą, ponieważ ta metoda nie działa zgodnie z reklamą. W rzeczywistości nie ma różnicy między tą pętlą a kodem w twojej odpowiedzi niż blok catch. Jak to wyjaśnisz? – EJP

+1

Należy pamiętać, że wszystkie wątki są synchronizowane na tym samym monitorze, dlatego inny wątek nie może tego zrobić. –