2010-08-30 11 views
10

Tengo un objeto responsable de la persistencia del estado JTable en el disco. Guarda/carga columnas visibles, su tamaño, posición, etc. A continuación se incluyen algunos bits interesantes de su definición de clase.Acceso a archivos sincronizado en el objeto Java

class TableSaver { 
    Timer timer = new Timer(true); 

    TableSaver() { 
     timer.schedule(new TableSaverTimerTask(), 15000, SAVE_STATE_PERIOD); 
    } 

    synchronized TableColumns load(PersistentTable table) { 
     String xml = loadFile(table.getTableKey()); 
     // parse XML, return 
    } 

    synchronized void save(String key, TableColumns value) { 
     try { 
      // Some preparations 
      writeFile(app.getTableConfigFileName(key), xml); 
     } catch (Exception e) { 
      // ... handle 
     } 
    } 

    private class TableSaverTimerTask extends TimerTask { 
     @Override 
     public void run() { 
      synchronized (TableSaver.this) { 
       Iterator<PersistentTable> iterator = queue.iterator(); 
       while (iterator.hasNext()) { 
        PersistentTable table = iterator.next(); 
        if (table.getTableKey() != null) { 
         save(table.getTableKey(), dumpState(table)); 
        } 
        iterator.remove(); 
       } 
      } 
     } 
    } 
} 
  • Sólo existe una instancia de TableSaver, nunca.
  • load() pueden invocarse desde varios hilos. El temporizador es claramente otro hilo.
  • loadFile() y writeFile() no dejan cadenas abiertas de archivos: usan una biblioteca robusta, bien probada y ampliamente utilizada que siempre cierra las transmisiones con try ... finally.

A veces esto falla con una excepción como:

java.lang.RuntimeException: java.io.FileNotFoundException: C:\path\to\table-MyTable.xml (The requested operation cannot be performed on a file with a user-mapped section open) 
    at package.FileUtil.writeFile(FileUtil.java:33) 
    at package.TableSaver.save(TableSaver.java:175) 
    at package.TableSaver.access$600(TableSaver.java:34) 
    at package.TableSaver$TableSaverTimerTask.run(TableSaver.java:246) 
    at java.util.TimerThread.mainLoop(Unknown Source) 
    at java.util.TimerThread.run(Unknown Source) 
Caused by: java.io.FileNotFoundException: C:\path\to\table-MyTable.xml (The requested operation cannot be performed on a file with a user-mapped section open) 
    at java.io.FileOutputStream.open(Native Method) 
    at java.io.FileOutputStream.<init>(Unknown Source) 
    at java.io.FileOutputStream.<init>(Unknown Source) 
    at package.FileUtilWorker.writeFile(FileUtilWorker.java:57) 
    ... 6 more 

así que tengo dos preguntas:

  1. ¿Cómo puede este tipo de sincronización fallar? Tenga en cuenta que estoy seguro de que solo hay una instancia de TableSaver.
  2. ¿Qué es esto en el stacktrace: package.TableSaver.access$600(TableSaver.java:34)? La línea 34 es la línea con class TableSaver {. ¿Puede ser esta la razón por la cual la sincronización no funciona?
+0

puede publicar su 'writefile() 'código? –

+0

Con respecto al n. ° 2, no hay problema. Eso es solo un artefacto de compilación de llamar a un miembro privado de la clase envolvente de una clase interna anidada. –

Respuesta

9

Google me entera de que esto parece ser específica de Windows. He aquí un extracto de Bug 6354433:

Esto es cuestión plataforma Windows con el archivo asignado en memoria, es decir MappedByteBuffer. El documento de Java 5.0 para FileChannel establece que "el búfer y el mapeo que representa permanecerán válidos hasta que el búfer en sí mismo se recolecte basura". El error ocurre cuando intentamos volver a abrir el almacén de archivos y el búfer de bytes asignado no ha sido GC. Dado que no existe el método unmap() para el búfer de bytes mapeado (ver error 4724038), estamos a merced del sistema operativo subyacente cuando el búfer se libera. Llamar al System.gc() puede liberar el búfer, pero no es garantía. El problema no ocurre en Solaris; puede deberse a la forma en que se implementa la memoria compartida en Solaris. Por lo tanto, la solución alternativa para Windows no es utilizar un archivo mapeado en memoria para las tablas de información de transacción.

¿Qué versión de Java/Windows está utilizando? ¿Tiene las últimas actualizaciones?

Aquí hay otros dos errores relacionados con algunas ideas útiles:

  • Bug 4715154 - archivo de memoria asignada no se pueden eliminar.
  • Bug 4469299 - Los archivos de memoria asignados no tienen GC.

En cuanto a su segunda pregunta, eso es sólo el nombre de clase autogenerada de una clase interna o anónima.

+0

¿Qué sucede si estoy usando 'java.io.FileOutputStream' y' java.io.FileInputStream' y no NIO? –

+0

Está utilizando NIO debajo de las capuchas. – BalusC

+0

Entonces, ¿qué deberíamos usar entonces? ¿Me puede decir que estoy teniendo el mismo problema en el escritorio de Windows 7, mientras que con la computadora portátil estaba bien, en Linux no hay ningún problema. Utilicé printwriter, bufferedwriter y nio, todos tenían el mismo problema –

2

Suponiendo que no haya problemas con el código, he visto que esto ocurra cuando un antivirus se ejecuta en segundo plano y abre alegremente los archivos para escanearlos entre bastidores. Si tiene un escáner de virus residente en la memoria que comprueba los archivos en segundo plano, intente deshabilitarlo o, al menos, deshabilitarlo para el directorio desde el que lee/escribe.

0

tuve este problema con algo de código Java firmemente roscado. Eché un vistazo a la conversación .NET referenciada y el centavo cayó. Es simplemente que tengo una disputa por el mismo archivo, entre diferentes hilos. Mirando más de cerca, la afirmación es (también) para algunos aspectos internos también. Así que mi mejor opción es synchronize-d alrededor del objeto compartido cuando la actualices.

Esto funciona y el error se disuelve en la niebla.

private static ShortLog tasksLog  = new ShortLog("filename"); 
    private static Boolean tasksLogLock = false; 

     ... 

    synchronized(tasksLogLock){ 
     tasksLog.saveLastDatum(this.toString()); 
    } 

ver también:

Cuestiones relacionadas