2017-10-09 30 views
1

我有多个线程来调用一个方法将内容从一个对象写入文件,如下所示: 当我使用1个线程来测试此方法时,将输出到我的文件中。但是,对于多个线程,输出到文件的内容很杂乱。如何使这个线程安全?如何使写入方法线程安全?

void (Document doc, BufferedWriter writer){ 
     Map<Sentence, Set<Matrix>> matrix = doc.getMatrix(); 
     for(Sentence sentence : matrix.keySet()){ 
      Set<Matrix> set = doc.getMatrix(sentence); 
      for(Matrix matrix : set){ 
       List<Result> results = ResultGenerator.getResult(); 
       writer.write(matrix, matrix.frequency()); 
       writer.write(results.toString()); 
       writer.write("\n"); 
      } 
     } 
} 

编辑:

我加入这一行List<Result> results = ResultGenerator.getResult()。我真正想要的是使用多个线程来处理此方法调用,因为这部分代价昂贵且需要大量时间。写作部分非常快,我并不需要多个线程。

鉴于这种变化,有没有办法让这个方法在并发环境中调用安全?

+0

你可以让它“同步”,但也许你应该重新思考你的逻辑。你是否真的需要多个线程写入同一个文件? – Gabriel

+5

写入相同的输出目标本质上是不安全的。解释你认为你需要的原因会有所帮助;一个可能的解决方案是使用单个阅读器将文档发布到并发队列中。 – chrylis

+0

@Gabriel,请参阅我的'编辑',并提供您的建议。 – user697911

回答

1

我不熟悉Java,所以我将提供一个语言不可知的答案。

你想要做的是将矩阵转换为结果,然后将它们格式化为字符串,最后将它们全部写入流中。

当您在处理每个结果后立即写入流,因此,当您向逻辑中添加多线程时,最终会在流中产生竞争条件。

您已经知道只有ResultGenerator.getResult()的调用应该并行完成,而流仍然需要按顺序访问。

现在你只需要把它付诸实践。这么做是为了:

  • 建立一个列表,其中每个项目是你需要生成一个结果
  • 处理这个名单并行由此产生的所有结果(这是一个map操作)的东西。您的项目列表将成为结果列表。
  • 现在你已经有了结果,所以你可以依次循环它们来格式化并将它们写入流。

我怀疑Java 8提供了一些工具来以功能方式创建所有东西,但正如我说的,我不是一个Java人,所以我不能提供代码示例。我希望这个解释就足够了。

@edit

F#中的示例代码解释了我的意思。

open System 

// This is a pretty long and nasty operation! 
let getResult doc = 
    Threading.Thread.Sleep(1000) 
    doc * 10 

// This is writing into stdout, but it could be a stream... 
let formatAndPrint = 
    printfn "Got result: %O" 

[<EntryPoint>] 
let main argv = 
    printfn "Starting..." 

    [| 1 .. 10 |] // A list with some docs to be processed 
    |> Array.Parallel.map getResult // Now that's doing the trick 
    |> Array.iter formatAndPrint 

    0 
-2

我会让它同步。在这种情况下,应用程序中只允许有一个线程同时调用这个方法=>没有混乱的输出。如果你有多个应用程序在运行,你应该考虑像文件锁定。

示例同步方法:

public synchronized void myMethod() { 
    // ... 
} 

此方法是专用于每个线程。

-2

您可以锁定一个方法,然后在完成时解锁它。通过在方法之前进行同步,可以确保一次只有一个线程可以执行它。同步会降低Java的速度,因此只能在必要时使用。

ReentrantLock lock = new ReentrantLock(); 

/* synchronized */ 
public void run(){ 

    lock.lock(); 

    System.out.print("Hello!"); 

    lock.unlock(); 

} 

这会锁定方法,就像synchronized一样。您可以使用它而不是同步,这就是为什么同步注释在上面。

1

如果您需要预定的顺序的最终文件,不要多线程,否则你不会得到你所期望的。

如果您认为使用多线程技术,您的程序将在I/O输出方面执行得更快,您可能会错误;由于同步造成的锁定或开销,您实际上会比单个线程的性能下降。

如果您试图编写一个非常大的文件,那么Document实例的排列顺序是不相关的,您认为您的编写器方法会碰到一个CPU瓶颈问题(但是我可以从我们的代码中找到的唯一可能原因是frequency()方法调用),你可以做的是让每个线程都拥有自己的写入临时文件的BufferedWriter,然后添加一个等待所有的线程的附加线程,然后使用连接生成最终的文件。

2

基本上,你在最后被单个文件限制。没有全局变量,它什么也不发布,所以这个方法是线程安全的。

但是,如果处理花费很多时间,则可以使用parallelstreams并将结果发布到concurrenthashmap或阻塞队列。然而,你仍然有一个消费者写入文件。

+0

如果他将同一个作者传递给多个调用,则该方法是不安全的。 – chrylis

+0

没错,但那本质上是不安全的。我在这里暗示的是他应该将结果发布到阻塞队列,然后单个使用者应该写入文件。此外,方法定义中没有任何内容假定作者是共享的黑白电话。 –

+0

不,但对问题的描述加上传递作者是一个大红旗。 – chrylis

0

如果你的代码使用不同的doc和writer对象,那么你的方法已经是线程安全的,因为它不访问和使用实例变量。

如果你正在写经过同一位作家的对象的方法,你可以使用这些方法之一,根据您的需要:

void (Document doc, BufferedWriter writer){ 
     Map<Sentence, Set<Matrix>> matrix = doc.getMatrix(); 
     for(Sentence sentence : matrix.keySet()){ 
      Set<Matrix> set = doc.getMatrix(sentence); 
      for(Matrix matrix : set){ 
       List<Result> results = ResultGenerator.getResult(); 

       // ensure that no other thread interferes while the following 
       // three .write() statements are executed. 
       synchronized(writer) { 
        writer.write(matrix, matrix.frequency()); // from your example, but I doubt it compiles 
        writer.write(results.toString()); 
        writer.write("\n"); 
       } 
      } 
     } 
} 

或使用临时StringBuilder对象无锁:

void (Document doc, BufferedWriter writer){ 
     Map<Sentence, Set<Matrix>> matrix = doc.getMatrix(); 
     StringBuilder sb = new StringBuilder(); 
     for(Sentence sentence : matrix.keySet()){ 
      Set<Matrix> set = doc.getMatrix(sentence); 
      for(Matrix matrix : set){ 
       List<Result> results = ResultGenerator.getResult(); 
       sb.append(matrix).append(matrix.frequency()); 
       sb.append(results.toString()); 
       sb.append("n"); 
      } 
     } 
     // write everything at once 
     writer.write(sb.toString(); 
}