2012-04-02 32 views
13

私は20スレッドを使用するJavaプログラムを持っています。それらのすべては結果をoutput.txtというファイルに書き込みます。スレッドとファイル書き込み

output.txtには常に異なる数の行が表示されます。

スレッドの同期に問題がありますか?これを処理する方法はありますか?

+0

これは、実装がどのようになっているかはあまり明確ではありません。私の単純なテストケースが示すように、私はFileWriterを使って、20のスレッドで一定の出力を得ています。実装の詳細を追加する必要があるかもしれません。私の答えを見てください。 – FaithReaper

答えて

26

スレッドの同期の問題がありますか?

はい。

これを処理する方法はありますか?

はい、関連するミューテックスで同期することで書き込みがシリアル化されていることを確認してください。あるいは、実際にファイルに出力するのはの1つのスレッドだけで、他のすべてのスレッドは、書き込みスレッドが描画するキューに書き込まれるテキストをキューに入れるだけです。 (。そうすれば20件のメインスレッドがI/Oでブロックされません)

ミューテックス日時:たとえば、それらはすべて私が参照してくださいよこれは、同じFileWriterインスタンス(または何でも)を使用している場合はfwように、その後、彼らはミューテックスとしてそれを使用することができます。彼らはそれぞれFileWriterまたは何自分自身を使用している場合

synchronized (fw) { 
    fw.write(...); 
} 

、彼らはすべての共有がmutexする何かを見つけます。

しかし、もう一度、他人のためにI/Oを行うスレッドを持つことは、おそらく良い方法です。

+0

あなたは、1つのライタースレッドアプローチのための最良のアプローチとなるものについて、より具体的に教えてください。例えば。多分[シングルスレッドExecutor](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor--)を使用して、その 'Executor '? – Roland

9

私はこの方法で整理することをお勧めします:1つのスレッドコンシューマは、すべてのデータを消費し、ファイルに書き込みます。すべてのワーカースレッドは、消費者スレッドに同期的にデータを生成します。または、複数のスレッドファイルの書き込みでは、いくつかのmutexまたはロックの実装を使用できます。

+1

+1あなたの記事を書いていたときに、私はこの提案を自分の答えに追加していました。 :-) –

1

この場合、同期を使用する必要があります。 2つのスレッド(t1とt2)が同時にファイルを開き、そのスレッドに書き始めるとします。最初のスレッドによって行われた変更は、2番目のスレッドがファイルへの変更を最後に保存するため、2番目のスレッドによって上書きされます。スレッドt1がファイルに書き込みを行っているとき、t2はt1がタスクを終了してからt2が開くまで待機しなければなりません。

2

パフォーマンスの面白さと管理のしやすさが必要な場合は、Alexなどで提案されているように、プロデューサ - コンシューマキューとファイルライタを1つだけ使用してください。ミューテックスを持つファイルのすべてのスレッドをちょうど乱暴にする - すべてのディスク遅延は(競合が追加されて)メインのアプリケーション機能に直接転送されます。これは、警告なしに消え去る傾向のある低速のネットワークドライブでは、特に不愉快です。

1

あなたがFileOutputStreamとして、あなたのファイルを保持することができます場合は、このようにそれをロックすることができます。

FileOutputStream file = ... 
.... 
// Thread safe version. 
void write(byte[] bytes) { 
    try { 
    boolean written = false; 
    do { 
     try { 
     // Lock it! 
     FileLock lock = file.getChannel().lock(); 
     try { 
      // Write the bytes. 
      file.write(bytes); 
      written = true; 
     } finally { 
      // Release the lock. 
      lock.release(); 
     } 
     } catch (OverlappingFileLockException ofle) { 
     try { 
      // Wait a bit 
      Thread.sleep(0); 
     } catch (InterruptedException ex) { 
      throw new InterruptedIOException ("Interrupted waiting for a file lock."); 
     } 
     } 
    } while (!written); 
    } catch (IOException ex) { 
    log.warn("Failed to lock " + fileName, ex); 
    } 
} 
+0

これは 'synchronized'が存在するので完全に冗長です。 – EJP

+0

@EJP - [FileLock](https://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileLock.html)を参照してください。*ファイルに保持されているロックは、すべてのユーザーに表示される必要がありますこれらのプログラムが記述されている言語にかかわらず、ファイルにアクセスできるプログラム* - 理論的には、 'synchronized'よりも優れているべきですが、しばしばそうではありません。 – OldCurmudgeon

+0

それはそれを非冗長にしたり、「同期よりも優れています」。質問には他のプロセスや他の言語については何もありません。 – EJP

0

まあ、いずれかの実装の詳細なしで、知ることは難しいですが、私のテストケースが示すように、私は常に取得220ラインの出力、すなわち一定のライン数、FileWriter。ここでは​​は使用されません。

import java.io.File; 
import java.io.FileWriter; 
import java.io.IOException; 
/** 
* Working example of synchonous, competitive writing to the same file. 
* @author WesternGun 
* 
*/ 
public class ThreadCompete implements Runnable { 
    private FileWriter writer; 
    private int status; 
    private int counter; 
    private boolean stop; 
    private String name; 


    public ThreadCompete(String name) { 
     this.name = name; 
     status = 0; 
     stop = false; 
     // just open the file without appending, to clear content 
     try { 
      writer = new FileWriter(new File("test.txt"), true); 
     } catch (IOException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 

    } 


    public static void main(String[] args) { 

     for (int i=0; i<20; i++) { 
      new Thread(new ThreadCompete("Thread" + i)).start(); 
     } 
    } 

    private int generateRandom(int range) { 
     return (int) (Math.random() * range); 
    } 

    @Override 
    public void run() { 
     while (!stop) { 
      try { 
       writer = new FileWriter(new File("test.txt"), true); 
       if (status == 0) { 
        writer.write(this.name + ": Begin: " + counter); 
        writer.write(System.lineSeparator()); 
        status ++; 
       } else if (status == 1) { 
        writer.write(this.name + ": Now we have " + counter + " books!"); 
        writer.write(System.lineSeparator()); 
        counter++; 
        if (counter > 8) { 
         status = 2; 
        } 

       } else if (status == 2) { 
        writer.write(this.name + ": End. " + counter); 
        writer.write(System.lineSeparator()); 
        stop = true; 
       } 
       writer.flush(); 
       writer.close(); 
      } catch (IOException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

私が理解(およびテスト)のように、このプロセスに2つの段階があります。

  • は、プール内のすべてのスレッドがすべて作成され、ファイルをつかむ準備ができて、始めました。
  • そのうちの1人はそれをつかみ、私はそれが内部的にそれをロックしていると思います、2つのスレッドから来た内容のラインが決して結合されていないので、他のスレッドにアクセスできません。したがって、スレッドが書き込みを行っているときに、スレッドが完了するまで待っていて、おそらくファイルを解放する可能性があります。 したがって、競合状態は発生しません。
  • 他の中で最も速いものがファイルを取得して書き込みを開始します。

まあ、それはあなたの実装が異なる場合

だから、コードを表示し、我々はそれを打破するために助けることができる.....キューイングせずに、ただバスルームの外に待機している群衆のようなものです。

関連する問題