2015-09-15 14 views
5

Per creare un nuovo, unico nome, io uso il seguente codice:creazione di nuovi file contemporaneamente

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

Avanti, io uso il file ed eliminarlo. Quando faccio questo in una situazione multithread, talvolta ho eccezioni sul file.createNewFile():

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

Il seguente codice riproduce il problema (il più delle volte):

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

Il metodo getSomeBytes() solo genera una quantità di byte, il contenuto effettivo non è importante:

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

Quando eseguo questo codice, talvolta es va bene, ma il più delle volte, genera alcune eccezioni come l'output di seguito, per esempio:

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 

Tutte le idee? Ho provato su un computer Windows 8 con java 1.7.0_45 e 1.8.0_31, entrambi gli stessi risultati.

Non sono sicuro se il problema è lo stesso di this question, ma può essere. A mio parere, l'utilizzo di più thread nello stesso processo sembra essere una parte del problema, ma non posso esserne certo, tuttavia è più veloce riprodotto.

+2

'File.createTempFile()' può essere un approccio più pulito qui, indipendentemente dal fatto che il file sia effettivamente destinato a essere temporaneo. – Sneftel

+0

@ Sneftel: Sono d'accordo, tuttavia, il nome del file è importante quindi non posso utilizzare File.createTempFile qui – Steven

risposta

5

sembra che su piattaforma Windows createNewFile potrebbe in modo casuale non riuscire se il file con lo stesso nome è stato appena cancellato anche su applicazioni single-threaded. Vedi this question per i dettagli. Per risolvere il problema, puoi provare a ignorare IOException dal createNewFile e continuare. Qualcosa di simile a questo:

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

Si noti che non è necessario controllare file.exists() chiamata come createNewFile() restituisce comodamente sia che ha creato il file correttamente.

Si noti che se si controllano tutti i file temporanei creati e non si cura del nome esatto del file, in genere non è necessario bloccare. Si può semplicemente usare il valore globale AtomicLong per ottenere il nome del file successivo o aggiungere un ID filo al nome del file.

+0

Poiché il problema è probabilmente causato da un fattore esterno, suppongo che tenterò/mi riprenderò dal problema in questo caso – Steven

0

Il loop non è fail-safe. C'è un problema con la finestra temporale. Dovrebbe essere più simile a questo:

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

Potresti essere un po 'più dettagliato su cosa sia la "finestra di temporizzazione" in questa sezione sincronizzata? Anche il tuo codice fisso fallisce nello stesso modo del codice OPs. –

+0

@TagirValeev Esiste una finestra temporale tra 'exists()' e 'createNewFile()' durante la quale il file può essere creato da un altro thread. C'è anche un errore nel controllare il risultato di 'createNewFile()'. Se il ciclo a due linee che ho postato qui non funziona, ci deve essere un problema di piattaforma con questo metodo che non funziona come pubblicizzato. Non c'è infatti differenza tra questo ciclo e il codice nella tua risposta rispetto al blocco catch. Come lo spieghi? – EJP

+1

Si noti che tutti i thread sono sincronizzati sullo stesso monitor, quindi un altro thread non può farlo. –