2011-12-07 1060 views
8

¿Es seguro llamar al write en Java FileOutputStream objeto de múltiples subprocesos? ¿La salida se serializará correctamente?Escribir en FileOutputStream desde varios subprocesos en Java

aclaración:

En mi caso, el registrador de clase contiene una referencia FileOutputStream, y múltiples hilos puede llamar registrador de escritura, que da formato a la salida y llama FileOutputStream escritura.

¿Debo sincronizar mi método de escritura del registrador para garantizar que los mensajes de múltiples hilos no estén mezclados?

+2

es posible que desee buscar en [FileChannel] (http://docs.oracle.com/javase/6/docs/api/java /nio/channels/FileChannel.html) – Nerdtron

+0

Respondo en segundo lugar la respuesta de Nerdtron. La solución Java nio FileChannel es, de lejos, la más simple de implementar. –

Respuesta

3

Un archivo no se puede abrir más de una vez en write-mode, por lo que la respuesta es no.

Después de ver su edición, sí debe introducir la sincronización en su registrador para asegurarse de que se accede a la secuencia solo por un hilo a la vez. Solo una sugerencia, ¿por qué no vas por Log4J? Ya maneja su caso de uso.

+0

En mi caso, el registrador de clase contiene una referencia FileOutputStream, y varios hilos pueden invocar la escritura del registrador, que formatea la salida y llama a FileOutputStream write –

+0

@ José Acaba de editar mi respuesta, por favor échele un vistazo – GETah

+0

Acerca de log4j, que es parte de una biblioteca, hay una interfaz de registro y la implementación simple solo escribe en un archivo, las aplicaciones aún pueden usar log4J u otros, pero no quiero forzar esta dependencia para los casos simples. –

1

Si desea seguir ordenando (es decir, el mensaje 1 en la secuencia de salida llegó antes del mensaje 2) debe bloquear la transmisión. Esto a su vez reduce la concurrencia. (Todos los subprocesos se pondrán en cola en la cola de la cerradura/semáforo y esperarán a que la transmisión esté disponible para ellos)

Si solo está interesado en escribir en una secuencia al mismo tiempo y no le importa ordenar, puede tener buffers para cada hilo Cada hilo escribe en su propio búfer. Cuando el búfer está lleno, adquiere un bloqueo (que puede implicar la espera del bloqueo) en el flujo y vacía su contenido en el flujo.

Edición: Me acabo de dar cuenta de que, si le importa ordenar y aún desea realizar varios subprocesos, también puede escribir la hora en el flujo de salida en formato Unix (como un largo). Después de que la transmisión se descargue en algún otro contenedor, los contenidos se pueden ordenar en función del tiempo y usted debe tener un archivo ordenado.

4

Aquí hay una implementación simple de un registrador sincronizado utilizando el FileChannel java nio. En este ejemplo, los mensajes de registro están limitados a 1024 bytes. Puede ajustar la longitud del mensaje de registro cambiando el valor BUFFER_SIZE.

import java.io.File; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.nio.ByteBuffer; 
import java.nio.channels.FileChannel; 
import java.util.HashMap; 

/** 
* The MyLogger class abstracts the writing of log messages to a file. 
* This is a synchronized implementation due to the usage of java.nio.channels.FileChannel 
* which is used to write log messages to the log file. 
* 
* The MyLogger class maintains a HashMap of MyLogger instances per log file. 
* The Key is the MD5 hash of the log file path and the Value is the MyLogger instance for that log file. 
* 
*/ 
public final class MyLogger { 
    private static final int BUFFER_SIZE = 1024; 
    private static final int DIGEST_BASE_RADIX = 16; 
    private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 
    private static HashMap<String, MyLogger> sLoggerMap; 

    private FileChannel mLogOutputChannel; 
    private ByteBuffer mByteBuffer; 
    private String mLogDir; 
    private String mLogFileName; 

    /** 
    * Private constructor which creates our log dir and log file if they do not already already exist. 
    * If the log file exists, then it is opened in append mode. 
    * 
    * @param logDir 
    *   The dir where the log file resides 
    * @param logFileName 
    *   The file name of the log file 
    * @throws IOException 
    *    Thrown if the file could not be created or opened for writing. 
    */ 
    private MyLogger(String logDir, String logFileName) throws IOException { 
     mLogDir = logDir; 
     mLogFileName = logFileName; 

     // create the log dir and log file if they do not exist 
     FileOutputStream logFile; 
     new File(mLogDir).mkdirs(); 

     final String logFilePath = mLogDir + File.separatorChar + mLogFileName; 
     final File f = new File(logFilePath); 
     if(!f.exists()) { 
      f.createNewFile(); 
     } 
     logFile = new FileOutputStream(logFilePath, true); 

     // set up our output channel and byte buffer 
     mLogOutputChannel = logFile.getChannel(); 
     mByteBuffer = ByteBuffer.allocate(BUFFER_SIZE); 
    } 

    /** 
    * Writes the given log message to the log file that is represented by this MyLogger instance. 
    * If the log message could not be written to the log file an error is logged in the System log. 
    * 
    * @param logMessage 
    *   The log message to write to the log file. 
    */ 
    public void log(String logMessage) { 

     // write the log message to the log file 
     if (mLogOutputChannel != null) { 
      mByteBuffer.put(logMessage.getBytes()); 
      mByteBuffer.put(LINE_SEPARATOR.getBytes()); 
      mByteBuffer.flip(); 
      try { 
       mLogOutputChannel.write(mByteBuffer); 
       // ensure that the data we just wrote to the log file is pushed to the disk right away 
       mLogOutputChannel.force(true); 
      } catch (IOException e) { 
       // Could not write to log file output channel 
       e.printStackTrace(); 
       return; 
      } 
     } 

     if(mByteBuffer != null) { 
      mByteBuffer.clear(); 
     } 
    } 

    /** 
    * Get an instance of the MyLogger for the given log file. Passing in the same logDir and logFileName will result in the same MyLogger instance being returned. 
    * 
    * @param logDir 
    *   The directory path where the log file resides. Cannot be empty or null. 
    * @param logFileName 
    *   The name of the log file Cannot be empty or null. 
    * @return The instance of the MyLogger representing the given log file. Null is returned if either logDir or logFilename is null or empty string. 
    * @throws IOException 
    *    Thrown if the file could not be created or opened for writing. 
    */ 
    public static MyLogger getLog(String logDir, String logFileName) throws IOException { 
     if(logDir == null || logFileName == null || logDir.isEmpty() || logFileName.isEmpty()) { 
      return null; 
     } 

     if(sLoggerMap == null) { 
      sLoggerMap = new HashMap<String, MyLogger>(); 
     } 

     final String logFilePathHash = getHash(logDir + File.separatorChar + logFileName); 
     if(!sLoggerMap.containsKey(logFilePathHash)) { 
      sLoggerMap.put(logFilePathHash, new MyLogger(logDir, logFileName)); 
     } 

     return sLoggerMap.get(logFilePathHash); 
    } 

    /** 
    * Utility method for generating an MD5 hash from the given string. 
    * 
    * @param path 
    *   The file path to our log file 
    * @return An MD5 hash of the log file path. If an MD5 hash could not be generated, the path string is returned. 
    */ 
    private static String getHash(String path) { 
     try { 
      final MessageDigest digest = MessageDigest.getInstance("MD5"); 
      digest.update(path.getBytes()); 
      return new BigInteger(digest.digest()).toString(DIGEST_BASE_RADIX); 
     } catch (NoSuchAlgorithmException ex) { 
      // this should never happen, but just to make sure return the path string 
      return path; 
     } 
    } 
} 

Esta es la forma en que lo utilizaría:

MyLogger aLogger = MyLogger.getLog("/path/to/log/dir", "logFilename"); 
aLogger.log("my log message"); 
+0

Bueno, eso está fuera del alcance de la pregunta, solo pregunté si FileOutputStream.write estaba sincronizado. –

Cuestiones relacionadas