2011-12-07 20 views
8

writeをJavaのFileOutputStreamオブジェクトフォームの複数のスレッドで呼び出すことは安全ですか?出力は正しくシリアル化されますか?Javaの複数のスレッドからFileOutputStreamに書き込む

明確化:

私の場合は、クラスのロガーは、のFileOutputStreamの参照を保持しており、複数のスレッドは、出力をフォーマットしたFileOutputStreamの書き込みを呼び出しロガーの書き込みを、呼び出すことができます。

複数のスレッドからのメッセージが混在していないことを保証するために、ログ記録メソッドを同期する必要がありますか?

+2

あなたは[のFileChannel](http://docs.oracle.com/javase/6/docs/api/javaに見たいと思うかもしれません/nio/channels/FileChannel.html) – Nerdtron

+0

私はNerdtronの答えを2回返します。 Java nio FileChannelソリューションは、実装するのがはるかに簡単です。 –

答えて

3

write-modeにファイルを複数回開くことができないため、答えは「いいえ」です。

編集を確認したら、一度に1つのスレッドだけがストリームにアクセスできるように、同期をロガーに導入する必要があります。ただの提案、なぜあなたはLog4Jに行かないのですか?すでにあなたのユースケースを処理しています。

+0

私の場合、クラス・ロガーはFileOutputStream参照を保持し、複数のスレッドは出力をフォーマットして出力を呼び出し、FileOutputStreamを呼び出すことができます。 –

+0

@José私の答えを編集しましたので、一読してください – GETah

+0

log4jについて、ライブラリ、ログインターフェイスがあり、シンプルな実装はファイルに書き込むだけで、アプリケーションはlog4Jなどを使用できますが、単純なケースではこの依存関係を強制したくありません。 –

2

いいえ、Javaは複数のスレッドから同じストリームへのストリーミングをサポートしていません。

あなたが使用スレッドのストリームをしたい場合は、このサイトをチェックアウト:http://lifeinide.com/post/2011-05-25-threaded-iostreams-in-java/

を彼はよく物事を説明し、あなたがやりたいだろうThreadedOutputStream、のためのいくつかのサンプルコードがあります。

1

注文を続けたい場合(出力ストリームのメッセージ1がメッセージ2の前に来た場合)、ストリームをロックする必要があります。これは並行性を低下させます。 (すべてのスレッドはロック/セマフォのキューにエンキューされ、ストリームが使用可能になるまで待機します)

ストリームに同時に書き込みを行い、順序を気にしない場合は、各スレッド用のバッファを持つ各スレッドは独自のバッファに書き込みます。バッファがいっぱいになると、ストリーム上のロック(ロックを待つことを含む)を取得し、その内容をストリームに空にします。

編集: 私は、順序を気にしていても、マルチスレッドを望むならば、出力ストリームの時間を(長いものとして)unix形式で書き込むと、それが分かりました。ストリームが他のコンテナにフラッシュされた後、コンテンツは時間に基づいてソートされ、オーダーされたファイルを持つ必要があります。

4

ここでは、java nio FileChannelを使用した同期化されたロガーの簡単な実装を示します。 この例では、ログメッセージは1024バイトに制限されています。ログメッセージの長さは、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; 
     } 
    } 
} 

これは、あなたがそれを使用する方法である:

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

それは質問の範囲外です、私はFileOutputStream.writeが同期されたかどうか尋ねました。 –