我有多个线程来调用一个方法将内容从一个对象写入文件,如下所示: 当我使用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 :(得分:2)
基本上,最后你受到单个文件的限制。没有全局变量,它不发布任何内容,因此该方法是线程安全的。
但是,如果处理确实需要花费大量时间,您可以使用并行流并将结果发布到concurrenthashmap或阻塞队列。但是,您仍然只有一个消费者可以写入该文件。
答案 1 :(得分:1)
如果您需要按预定的顺序排列最终文件,请不要多线程,否则您将无法获得预期的结果。
如果您认为使用多线程程序,您的程序将在I / O输出方面执行得更快,您可能会错误;由于同步导致锁定或开销,实际上性能会比单个线程降低。
如果你试图编写一个非常大的文件,Document
实例的顺序是不相关的,你认为你的编写器方法会遇到CPU瓶颈(但是我能从中得出的唯一可能的原因)代码是frequency()
方法调用),你可以做的是让每个线程都拥有自己的BufferedWriter写入临时文件,然后添加一个等待所有的额外线程,然后使用连接生成最终文件。
答案 2 :(得分: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
答案 3 :(得分: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();
}
答案 4 :(得分:-2)
我让它同步。在这种情况下,只允许应用程序中的一个线程同时调用此方法=&gt;没有杂乱的输出。如果你有多个应用程序在运行,你应该考虑像文件锁定。
同步方法的示例:
sizeof
此方法对每个线程都是独占的。
答案 5 :(得分:-2)
您可以锁定方法,然后在完成后将其解锁。通过在方法之前放置synchronized,可以确保一次只有一个线程可以执行它。同步会降低Java的速度,因此只应在必要时使用它。
ReentrantLock lock = new ReentrantLock();
/* synchronized */
public void run(){
lock.lock();
System.out.print("Hello!");
lock.unlock();
}
这就像同步一样锁定方法。您可以使用它而不是同步,这就是为什么同步被注释掉的原因。