2017-10-09 59 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(); 
}